diff --git a/.env.example b/.env.example deleted file mode 100644 index 4ae028019..000000000 --- a/.env.example +++ /dev/null @@ -1,106 +0,0 @@ -#### QUIVR Configuration -# This file is used to configure the Quivr stack. It is used by the `docker-compose.yml` file to configure the stack. - -# API KEYS -# OPENAI. Update this to use your API key. To skip OpenAI integration use a fake key, for example: tk-aabbccddAABBCCDDEeFfGgHhIiJKLmnopjklMNOPqQqQqQqQ -OPENAI_API_KEY=your-openai-api-key -# ANTHROPIC_API_KEY=your-anthropic-api-key -# MISTRAL_API_KEY=your-mistral-api-key -# GROQ_API_KEY=your-groq-api-key - -COHERE_API_KEY=your-cohere-api-key -# JINA_API_KEY=your-jina-api-key - -# UNSTRUCTURED_API_KEY=your-unstructured-api-key -# UNSTRUCTURED_API_URL=https://api.unstructured.io/general/v0/general - -# LLAMA_PARSE_API_KEY=your-llamaparse-api-key - -# Configuration files path -BRAIN_CONFIG_PATH=config/retrieval_config_workflow.yaml -CHAT_LLM_CONFIG_PATH=config/chat_llm_config.yaml - -# LangSmith -# LANGCHAIN_TRACING_V2=true -# LANGCHAIN_ENDPOINT="https://api.smith.langchain.com" -# LANGCHAIN_API_KEY=your-langchain-api-key -# LANGCHAIN_PROJECT=your-langchain-project-name - -# LOCAL -# OLLAMA_API_BASE_URL=http://host.docker.internal:11434 # Uncomment to activate ollama. This is the local url for the ollama api - -######## -# FRONTEND -######## - -NEXT_PUBLIC_ENV=local -NEXT_PUBLIC_BACKEND_URL=http://localhost:5050 -NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 -NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 -NEXT_PUBLIC_CMS_URL=https://cms.quivr.app -NEXT_PUBLIC_FRONTEND_URL=http://localhost:* -NEXT_PUBLIC_AUTH_MODES=password -NEXT_PUBLIC_SHOW_TOKENS=false -#NEXT_PUBLIC_PROJECT_NAME= - - -######## -# BACKEND -######## - -LOG_LEVEL=INFO -SUPABASE_URL=http://host.docker.internal:54321 -EXTERNAL_SUPABASE_URL=http://localhost:54321 -SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU -PG_DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:54322/postgres -PG_DATABASE_ASYNC_URL=postgresql+asyncpg://postgres:postgres@host.docker.internal:54322/postgres -JWT_SECRET_KEY=super-secret-jwt-token-with-at-least-32-characters-long -AUTHENTICATE=true -TELEMETRY_ENABLED=true -CELERY_BROKER_URL=redis://redis:6379/0 -CELEBRY_BROKER_QUEUE_NAME=quivr-preview.fifo -QUIVR_DOMAIN=http://localhost:3000/ -BACKEND_URL=http://localhost:5050 -EMBEDDING_DIM=1536 -DEACTIVATE_STRIPE=true - - -# PARSEABLE LOGGING -USE_PARSEABLE=False -PARSEABLE_STREAM_NAME=quivr-api -PARSEABLE_URL= -PARSEABLE_AUTH= - -#RESEND -RESEND_API_KEY= -RESEND_EMAIL_ADDRESS=onboarding@resend.dev -RESEND_CONTACT_SALES_FROM=contact_sales@resend.dev -RESEND_CONTACT_SALES_TO= - -# SMTP -QUIVR_SMTP_SERVER=smtp.example.com -QUIVR_SMTP_PORT=587 -QUIVR_SMTP_USERNAME=username -QUIVR_SMTP_PASSWORD=password - -CRAWL_DEPTH=1 - -PREMIUM_MAX_BRAIN_NUMBER=30 -PREMIUM_MAX_BRAIN_SIZE=10000000 -PREMIUM_DAILY_CHAT_CREDIT=100 - -# BRAVE SEARCH API KEY -BRAVE_SEARCH_API_KEY=CHANGE_ME - - -# GOOGLE DRIVE -GOOGLE_CLIENT_ID=your-client-id -GOOGLE_CLIENT_SECRET=your-client-secret -GOOGLE_PROJECT_ID=your-project-id -GOOGLE_AUTH_URI=https://accounts.google.com/o/oauth2/auth -GOOGLE_TOKEN_URI=https://oauth2.googleapis.com/token -GOOGLE_AUTH_PROVIDER_CERT_URL=https://www.googleapis.com/oauth2/v1/certs -GOOGLE_REDIRECT_URI=http://localhost - -# SHAREPOINT -SHAREPOINT_CLIENT_ID=your-client-id diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 292006cb1..000000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -; Minimal configuration for Flake8 to work with Black. -max-line-length = 100 -ignore = E101,E111,E112,E221,E222,E501,E711,E712,W503,W504,F401 diff --git a/.github/workflows/aws-strapi.yml b/.github/workflows/aws-strapi.yml deleted file mode 100644 index 75c008db4..000000000 --- a/.github/workflows/aws-strapi.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Deploy Strapi - -on: - push: - branches: ["main"] - paths: - - "cms/**" - -env: - AWS_REGION: eu-west-3 - ECR_REPOSITORY: quivr-strapi - ECR_REGISTRY: 253053805092.dkr.ecr.eu-west-3.amazonaws.com - ECS_CLUSTER: quivr - -jobs: - build_and_push: - name: Build and Push Docker Image - runs-on: ubuntu-latest - environment: production - - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@2fc7aceee09e9e4a7105c0d060c656fad0b4f63d # v1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2 - - - name: Create Docker Cacha Storage Backend - run: | - docker buildx create --use --driver=docker-container - - name: See the file in the runner - run: | - ls -la - - name: Build, tag, and push image to Amazon ECR - id: build-image - uses: docker/build-push-action@0a97817b6ade9f46837855d676c4cca3a2471fc9 # v4 - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} - with: - context: ./cms/quivr/ - push: true - tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}, ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest, ghcr.io/quivrhq/quivr:latest - cache-from: type=gha - cache-to: type=gha,mode=max - - deploy: - needs: build_and_push - runs-on: ubuntu-latest - environment: production - strategy: - fail-fast: false - matrix: - include: - - name: "strapi" - service: "strapi" - task_definition: ".aws/task_definition_strapi.json" - container: "strapi" - - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Fill in the new image ID in the Amazon ECS task definition for ${{ matrix.name }} - id: task-def - uses: aws-actions/amazon-ecs-render-task-definition@4225e0b507142a2e432b018bc3ccb728559b437a # v1 - with: - task-definition: ${{ matrix.task_definition }} - container-name: ${{ matrix.container }} - image: ${{env.ECR_REGISTRY}}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} - - - name: Deploy Amazon ECS task definition for ${{ matrix.name }} - uses: aws-actions/amazon-ecs-deploy-task-definition@df9643053eda01f169e64a0e60233aacca83799a # v1 - with: - task-definition: ${{ steps.task-def.outputs.task-definition }} - service: ${{ matrix.service }} - cluster: ${{ env.ECS_CLUSTER }} - wait-for-service-stability: true diff --git a/.github/workflows/backend-core-tests.yml b/.github/workflows/backend-core-tests.yml index 5e067f142..013eb7c10 100644 --- a/.github/workflows/backend-core-tests.yml +++ b/.github/workflows/backend-core-tests.yml @@ -3,10 +3,10 @@ name: Run Tests with Tika Server on: push: paths: - - "backend/core/**" + - "core/**" pull_request: paths: - - "backend/core/**" + - "core/**" workflow_dispatch: jobs: @@ -30,7 +30,7 @@ jobs: working-directory: backend - name: 🔄 Sync dependencies run: | - cd backend + cd core UV_INDEX_STRATEGY=unsafe-first-match rye sync --no-lock - name: Run tests @@ -40,7 +40,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libmagic-dev poppler-utils libreoffice tesseract-ocr pandoc - cd backend + cd core rye run python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" rye run python -c "import nltk;nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng')" rye test -p quivr-core diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml deleted file mode 100644 index cc77192c6..000000000 --- a/.github/workflows/backend-tests.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Run Tests API and Worker - -on: - pull_request: - paths: - - "backend/**" - workflow_dispatch: - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - project: [quivr-api, quivr-worker] - steps: - - name: 👀 Checkout code - uses: actions/checkout@v2 - - - name: 🔨 Install the latest version of rye - uses: eifinger/setup-rye@v4 - with: - enable-cache: true - working-directory: backend - - - name: 🔄 Sync dependencies - run: | - cd backend - UV_INDEX_STRATEGY=unsafe-first-match rye sync --no-lock - - - name: 🚤 Install Supabase CLI - run: | - ARCHITECTURE=$(uname -m) - if [ "$ARCHITECTURE" = "x86_64" ]; then - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_amd64.deb - sudo dpkg -i supabase_1.163.6_linux_amd64.deb - elif [ "$ARCHITECTURE" = "aarch64" ]; then - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_arm64.deb - sudo dpkg -i supabase_1.163.6_linux_arm64.deb - fi - - - name: 😭 Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y libmagic-dev poppler-utils libreoffice tesseract-ocr pandoc - - - name: Install dependencies and run tests - env: - OPENAI_API_KEY: this-is-a-fake-openai-api-key - SUPABASE_URL: http://localhost:54321 - SUPABASE_SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU - PG_DATABASE_URL: postgresql://postgres:postgres@localhost:54322/postgres - PG_DATABASE_ASYNC_URL: postgresql+asyncpg://postgres:postgres@localhost:54322/postgres - ANTHROPIC_API_KEY: null - JWT_SECRET_KEY: super-secret-jwt-token-with-at-least-32-characters-long - AUTHENTICATE: true - TELEMETRY_ENABLED: true - CELERY_BROKER_URL: redis://redis:6379/0 - CELEBRY_BROKER_QUEUE_NAME: quivr-preview.fifo - QUIVR_DOMAIN: http://localhost:3000/ - BACKEND_URL: http://localhost:5050 - EMBEDDING_DIM: 1536 - run: | - cd backend - sed -i 's/enabled = true/enabled = false/g' supabase/config.toml - sed -i '/\[db\]/,/\[.*\]/s/enabled = false/enabled = true/' supabase/config.toml - sed -i '/\[storage\]/,/\[.*\]/s/enabled = false/enabled = true/' supabase/config.toml - supabase start - rye run python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" - rye run python -c "import nltk;nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng')" - rye test -p ${{ matrix.project }} diff --git a/.github/workflows/porter_stack_cdp-front.yml b/.github/workflows/porter_stack_cdp-front.yml deleted file mode 100644 index 01aa74c12..000000000 --- a/.github/workflows/porter_stack_cdp-front.yml +++ /dev/null @@ -1,29 +0,0 @@ -"on": - push: - branches: - - main -name: Deploy to cdp-front -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.porter.run - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_REPO_NAME: ${{ github.event.repository.name }} - PORTER_STACK_NAME: cdp-front - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_cdp.yml b/.github/workflows/porter_stack_cdp.yml deleted file mode 100644 index c3dcf2a4b..000000000 --- a/.github/workflows/porter_stack_cdp.yml +++ /dev/null @@ -1,29 +0,0 @@ -"on": - push: - branches: - - main -name: Deploy to cdp -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.porter.run - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_REPO_NAME: ${{ github.event.repository.name }} - PORTER_STACK_NAME: cdp - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_preview-frontend.yml b/.github/workflows/porter_stack_preview-frontend.yml deleted file mode 100644 index 915cb58e0..000000000 --- a/.github/workflows/porter_stack_preview-frontend.yml +++ /dev/null @@ -1,28 +0,0 @@ -"on": - push: - branches: - - main -name: Deploy to preview-frontend -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.getporter.dev - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_STACK_NAME: preview-frontend - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_preview.yml b/.github/workflows/porter_stack_preview.yml deleted file mode 100644 index 5351fc681..000000000 --- a/.github/workflows/porter_stack_preview.yml +++ /dev/null @@ -1,28 +0,0 @@ -"on": - push: - branches: - - main -name: Deploy to preview -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.getporter.dev - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_STACK_NAME: preview - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_production.yml b/.github/workflows/porter_stack_production.yml deleted file mode 100644 index 52a6ab630..000000000 --- a/.github/workflows/porter_stack_production.yml +++ /dev/null @@ -1,28 +0,0 @@ -on: - push: - tags: - - "v*" -name: Deploy to production -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.getporter.dev - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_STACK_NAME: production - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_raise-frontend.yml b/.github/workflows/porter_stack_raise-frontend.yml deleted file mode 100644 index 64b6af9d1..000000000 --- a/.github/workflows/porter_stack_raise-frontend.yml +++ /dev/null @@ -1,29 +0,0 @@ -on: - push: - tags: - - 'v*' -name: Deploy to raise-frontend -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.getporter.dev - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_REPO_NAME: ${{ github.event.repository.name }} - PORTER_STACK_NAME: raise-frontend - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/porter_stack_raise.yml b/.github/workflows/porter_stack_raise.yml deleted file mode 100644 index cea30c20c..000000000 --- a/.github/workflows/porter_stack_raise.yml +++ /dev/null @@ -1,29 +0,0 @@ -on: - push: - tags: - - 'v*' -name: Deploy to raise -jobs: - porter-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set Github tag - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup porter - uses: porter-dev/setup-porter@v0.1.0 - - name: Deploy stack - timeout-minutes: 30 - run: exec porter apply - env: - PORTER_CLUSTER: "3877" - PORTER_DEPLOYMENT_TARGET_ID: cd21d246-86df-49e0-ba0a-78e2802572e7 - PORTER_HOST: https://dashboard.getporter.dev - PORTER_PR_NUMBER: ${{ github.event.number }} - PORTER_PROJECT: "10983" - PORTER_REPO_NAME: ${{ github.event.repository.name }} - PORTER_STACK_NAME: raise - PORTER_TAG: ${{ steps.vars.outputs.sha_short }} - PORTER_TOKEN: ${{ secrets.PORTER_STACK_10983_3877 }} diff --git a/.github/workflows/prebuild-images.yml b/.github/workflows/prebuild-images.yml deleted file mode 100644 index a9859e884..000000000 --- a/.github/workflows/prebuild-images.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Prebuild & Deploy Docker Images - -on: - push: - branches: ["main"] - # paths: - # - "backend/**" - -env: - AWS_REGION: eu-west-3 - ECR_REPOSITORY: backend - ECR_REGISTRY: public.ecr.aws/c2l8c5w6 - -jobs: - build_and_push: - name: Build and Push Docker Image - runs-on: ubuntu-latest - environment: production - - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@2fc7aceee09e9e4a7105c0d060c656fad0b4f63d # v1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Docker Hub - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3 - - - name: Create Docker Cacha Storage Backend - run: | - docker buildx create --use --driver=docker-container - - name: See the file in the runner - run: | - ls -la - - name: Build, tag, and push image to Amazon ECR - id: build-image - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5 - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} - with: - context: ./backend/ - push: true - platforms: linux/amd64,linux/arm64 - # tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}, ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest, ghcr.io/quivrhq/quivr:latest, stangirard/quivr-backend-prebuilt:latest, stangirard/quivr-backend-prebuilt:${{ env.IMAGE_TAG }} - tags: ghcr.io/quivrhq/quivr:latest, stangirard/quivr-backend-prebuilt:latest, stangirard/quivr-backend-prebuilt:${{ env.IMAGE_TAG }} - cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/release-please-core.yml b/.github/workflows/release-please-core.yml index 0089ab43f..9fd2dbc5b 100644 --- a/.github/workflows/release-please-core.yml +++ b/.github/workflows/release-please-core.yml @@ -14,7 +14,7 @@ jobs: release-please: runs-on: ubuntu-latest outputs: - release_created: ${{ steps.release.outputs['backend/core--release_created'] }} + release_created: ${{ steps.release.outputs['core--release_created'] }} steps: - name: Checkout repository uses: actions/checkout@v3 @@ -30,7 +30,7 @@ jobs: id: release uses: google-github-actions/release-please-action@v4 with: - path: backend/core + path: core token: ${{ secrets.RELEASE_PLEASE_TOKEN }} @@ -40,14 +40,14 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: backend/ + working-directory: steps: - uses: actions/checkout@v4 - name: Install Rye uses: eifinger/setup-rye@v2 with: enable-cache: true - working-directory: backend/ + working-directory: core/ - name: Rye Sync run: cd core/ && UV_INDEX_STRATEGY=unsafe-first-match rye sync --no-lock - name: Rye Build diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index 8cd1c3273..000000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,22 +0,0 @@ -on: - push: - branches: - - main - -permissions: - contents: write - pull-requests: write - -name: release-please - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: google-github-actions/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3 - with: - release-type: node - changelog-notes-type: github - package-name: release-please-action - bump-patch-for-minor-pre-major: true - token: ${{ secrets.RELEASE_PLEASE_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test-build-image.yml b/.github/workflows/test-build-image.yml deleted file mode 100644 index 22e013a0d..000000000 --- a/.github/workflows/test-build-image.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Test Build Image - -on: - pull_request: - branches: ["main"] - paths: - - 'backend/**' - -jobs: - build: - name: Build Docker Image - runs-on: ubuntu-latest - strategy: - matrix: - platform: [linux/amd64, linux/arm64] - - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Login to GitHub Container Registry - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3 - - - name: Create Docker Cacha Storage Backend - run: | - docker buildx create --use --driver=docker-container - - - name: Build image - id: build-image - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5 - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} - with: - context: ./backend/ - platforms: ${{ matrix.platform }} - cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/vercel-preview.yml b/.github/workflows/vercel-preview.yml deleted file mode 100644 index 999d80a7c..000000000 --- a/.github/workflows/vercel-preview.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Preview Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - branches: [ "main" ] -jobs: - Deploy-Preview: - environment: production - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - with: - submodules: 'true' - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - - name: Build Project Artifacts - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/vercel.yml b/.github/workflows/vercel.yml deleted file mode 100644 index 687f951ce..000000000 --- a/.github/workflows/vercel.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Production Tag Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - # Pattern matched against refs/tags - tags: - - '*' # Push events to every tag not containing / -jobs: - - Deploy-Production: - environment: production - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - with: - submodules: 'true' - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - - name: Build Project Artifacts - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} \ No newline at end of file diff --git a/backend/api/.python-version b/.python-version similarity index 100% rename from backend/api/.python-version rename to .python-version diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f1f84edac..48689038c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,3 @@ { - "backend/core": "0.0.18", - ".": "0.0.322" + "core": "0.0.18" } \ No newline at end of file diff --git a/.run_tests.sh b/.run_tests.sh deleted file mode 100755 index 45874ff84..000000000 --- a/.run_tests.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -set -e - -## TESTS SUITES -test_suites=( - "Backend Core:cd backend/core && tox -p auto" - "Worker:cd backend && pytest worker" - "API:cd backend && pytest api" - ) - -# Check if gum is installed -if ! command -v gum &>/dev/null; then - echo "gum is not installed. Please install it with 'brew install gum'." - exit 1 -fi - -root_dir=$(pwd) - -# Function to check if Tika server is running -check_tika_server() { - if nc -z localhost 9998 >/dev/null 2>&1; then - return 0 - else - gum style --foreground 196 "Error: Tika server is not running on port 9998." - gum style --foreground 226 "Please start the Tika server before running the tests." - gum style --foreground 226 "Run 'docker run -d -p 9998:9998 apache/tika' to start the Tika server." - return 1 - fi -} - -# select test suites to run, either all or one of the following -get_test_suites_to_run() { - gum style --bold "Select test suites to run:" - options=("All" "${test_suites[@]%%:*}") - selected=$(gum choose "${options[@]}") - if [[ "$selected" == "All" ]]; then - gum style --bold "Running all test suites" - else - # Find the matching test suite - for suite in "${test_suites[@]}"; do - if [[ "${suite%%:*}" == "$selected" ]]; then - test_suites=("$suite") - break - fi - done - fi -} - -# Function to run a single test suite -run_test_suite() { - local suite_name=$1 - local command=$2 - local exit_code - - gum style --border normal --border-foreground 99 --padding "1 2" --bold "$suite_name Tests" - eval "$command" - exit_code=$? - cd "$root_dir" - - if [ $exit_code -eq 0 ]; then - gum style --foreground 46 "$suite_name Tests: PASSED" - else - gum style --foreground 196 "$suite_name Tests: FAILED" - fi - - return $exit_code -} - -run_tests() { - get_test_suites_to_run - # gum spin --spinner dot --title "Running tests..." -- sleep 1 - - local all_passed=true - local results=() - - for suite in "${test_suites[@]}"; do - IFS=':' read -r suite_name suite_command <<< "$suite" - if ! run_test_suite "$suite_name" "$suite_command"; then - all_passed=false - fi - results+=("$suite_name:$?") - done - - # Print summary of test results - gum style --border double --border-foreground 99 --padding "1 2" --bold "Test Summary" - for result in "${results[@]}"; do - IFS=':' read -r suite_name exit_code <<< "$result" - if [ "$exit_code" -eq 0 ]; then - gum style --foreground 46 "✓ $suite_name: PASSED" - else - gum style --foreground 196 "✗ $suite_name: FAILED" - fi - done - - # Return overall exit code - $all_passed -} - -# Main execution -if check_tika_server; then - run_tests - exit $? -else - exit 1 -fi diff --git a/Makefile b/Makefile deleted file mode 100644 index af29034c3..000000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -.DEFAULT_TARGET=help - -## help: Display list of commands -.PHONY: help -help: - @echo "Available commands:" - @sed -n 's|^##||p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's|^| |' - - -## dev: Start development environment -.PHONY: dev -dev: - DOCKER_BUILDKIT=1 docker compose -f docker-compose.dev.yml up --build - -dev-build: - DOCKER_BUILDKIT=1 docker compose -f docker-compose.dev.yml build --no-cache - DOCKER_BUILDKIT=1 docker compose -f docker-compose.dev.yml up - -## prod: Build and start production environment -.PHONY: prod -prod: - docker compose -f docker-compose.yml up --build - -## front: Build and start frontend -.PHONY: front -front: - cd frontend && yarn && yarn build && yarn start - -## test: Run tests -.PHONY: test -test: - # Ensure dependencies are installed with dev and test extras - # poetry install --with dev,test && brew install tesseract pandoc libmagic - ./.run_tests.sh diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 0757494bb..000000000 --- a/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] - -[requires] -python_version = "3.11" diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index c6f0e1816..000000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,14 +0,0 @@ -**/.mypy_cache -**/__pycache__ -*/.pytest_cache -**/__pycache__ -**/.benchmarks/ -**/.cache/ -**/.pytest_cache/ -**/.next/ -**/build/ -**/.docusaurus/ -**/node_modules/ -**/.venv/ -**/.tox/ -**/.tox-docker/ diff --git a/backend/.vscode/launch.json b/backend/.vscode/launch.json deleted file mode 100644 index daab14f5a..000000000 --- a/backend/.vscode/launch.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Remote Attach", - "type": "python", - "request": "attach", - "connect": { - "host": "localhost", - "port": 5678 - }, - "pathMappings": [ - { - "localRoot": "${workspaceFolder}", - "remoteRoot": "." - } - ], - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index b44c972a8..000000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -FROM python:3.11.6-slim-bullseye - -WORKDIR /app - -# Install runtime dependencies -RUN apt-get clean && apt-get update && apt-get install -y \ - libgeos-dev \ - libcurl4-openssl-dev \ - libssl-dev \ - binutils \ - curl \ - git \ - autoconf \ - automake \ - build-essential \ - libtool \ - python-dev \ - build-essential \ - wget \ - # Additional dependencies for document handling - libmagic-dev \ - poppler-utils \ - tesseract-ocr \ - libreoffice \ - libpq-dev \ - gcc \ - libhdf5-serial-dev \ - pandoc && \ - rm -rf /var/lib/apt/lists/* && apt-get clean - -# Install Supabase CLI -RUN ARCHITECTURE=$(uname -m) && \ - if [ "$ARCHITECTURE" = "x86_64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_amd64.deb && \ - dpkg -i supabase_1.163.6_linux_amd64.deb && \ - rm supabase_1.163.6_linux_amd64.deb; \ - elif [ "$ARCHITECTURE" = "aarch64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_arm64.deb && \ - dpkg -i supabase_1.163.6_linux_arm64.deb && \ - rm supabase_1.163.6_linux_arm64.deb; \ - fi - -COPY requirements.lock pyproject.toml README.md ./ -COPY api/pyproject.toml api/README.md ./api/ -COPY api/quivr_api/__init__.py ./api/quivr_api/__init__.py -COPY core/pyproject.toml core/README.md ./core/ -COPY core/quivr_core/__init__.py ./core/quivr_core/__init__.py -COPY worker/pyproject.toml worker/README.md ./worker/ -COPY worker/quivr_worker/__init__.py ./worker/quivr_worker/__init__.py -COPY worker/diff-assistant/pyproject.toml worker/diff-assistant/README.md ./worker/diff-assistant/ -COPY worker/diff-assistant/quivr_diff_assistant/__init__.py ./worker/diff-assistant/quivr_diff_assistant/__init__.py - -RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -r requirements.lock - -RUN playwright install --with-deps && \ - python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" && \ - python -c "import nltk;nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng')" - - -ENV PYTHONPATH=/app - -COPY . . -EXPOSE 5050 diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev deleted file mode 100644 index 01c4a51c6..000000000 --- a/backend/Dockerfile.dev +++ /dev/null @@ -1,48 +0,0 @@ -# Using a slim version for a smaller base image -FROM python:3.11.6-slim-bullseye - -WORKDIR /app -# Install GEOS library, Rust, and other dependencies, then clean up -RUN apt-get clean && apt-get update && apt-get install -y \ - libgeos-dev \ - libcurl4-openssl-dev \ - libssl-dev \ - binutils \ - curl \ - git \ - autoconf \ - automake \ - build-essential \ - libtool \ - python-dev \ - build-essential \ - # Additional dependencies for document handling - libmagic-dev \ - poppler-utils \ - tesseract-ocr \ - libreoffice \ - libpq-dev \ - gcc \ - libhdf5-serial-dev \ - pandoc && \ - rm -rf /var/lib/apt/lists/* && apt-get clean - -COPY requirements.lock pyproject.toml README.md ./ -COPY api/pyproject.toml api/README.md ./api/ -COPY api/quivr_api/__init__.py ./api/quivr_api/__init__.py -COPY core/pyproject.toml core/README.md ./core/ -COPY core/quivr_core/__init__.py ./core/quivr_core/__init__.py -COPY worker/pyproject.toml worker/README.md ./worker/ -COPY worker/quivr_worker/__init__.py ./worker/quivr_worker/__init__.py -COPY worker/diff-assistant/pyproject.toml worker/diff-assistant/README.md ./worker/diff-assistant/ -COPY worker/diff-assistant/quivr_diff_assistant/__init__.py ./worker/diff-assistant/quivr_diff_assistant/__init__.py - -RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -r requirements.lock - -RUN playwright install --with-deps && \ - python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" && \ - python -c "import nltk;nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng')" - -COPY . . - -EXPOSE 5050 diff --git a/backend/api/Dockerfile b/backend/api/Dockerfile deleted file mode 100644 index a68079df2..000000000 --- a/backend/api/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# Using a slim version for a smaller base image -FROM python:3.11.6-slim-bullseye - -ARG DEV_MODE -ENV DEV_MODE=$DEV_MODE - -RUN apt-get clean && apt-get update && apt-get install -y wget curl - -RUN ARCHITECTURE=$(uname -m) && \ - if [ "$ARCHITECTURE" = "x86_64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_amd64.deb && \ - dpkg -i supabase_1.163.6_linux_amd64.deb && \ - rm supabase_1.163.6_linux_amd64.deb; \ - elif [ "$ARCHITECTURE" = "aarch64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_arm64.deb && \ - dpkg -i supabase_1.163.6_linux_arm64.deb && \ - rm supabase_1.163.6_linux_arm64.deb; \ - fi && \ - rm -rf /var/lib/apt/lists/* - -# TODO(@aminediro) : multistage build. Probably dont neet poetry once its built -# Install Poetry -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false && \ - poetry config virtualenvs.in-project false - -# Add Rust binaries to the PATH -ENV PATH="/root/.cargo/bin:${PATH}" \ - POETRY_CACHE_DIR=/tmp/poetry_cache \ - PYTHONDONTWRITEBYTECODE=1 \ - POETRY_VIRTUALENVS_PATH=/code/api/.venv-docker - -WORKDIR /code/api -COPY . /code/ - -RUN poetry install && rm -rf $POETRY_CACHE_DIR - -ENV PYTHONPATH=/code/api - -EXPOSE 5050 diff --git a/backend/api/Dockerfile.dev b/backend/api/Dockerfile.dev deleted file mode 100644 index 5495e8a1f..000000000 --- a/backend/api/Dockerfile.dev +++ /dev/null @@ -1,45 +0,0 @@ -# Using a slim version for a smaller base image -FROM python:3.11.6-slim-bullseye - -ARG DEV_MODE -ENV DEV_MODE=$DEV_MODE - -RUN apt-get clean && apt-get update && apt-get install -y wget curl - -RUN ARCHITECTURE=$(uname -m) && \ - if [ "$ARCHITECTURE" = "x86_64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_amd64.deb && \ - dpkg -i supabase_1.163.6_linux_amd64.deb && \ - rm supabase_1.163.6_linux_amd64.deb; \ - elif [ "$ARCHITECTURE" = "aarch64" ]; then \ - wget https://github.com/supabase/cli/releases/download/v1.163.6/supabase_1.163.6_linux_arm64.deb && \ - dpkg -i supabase_1.163.6_linux_arm64.deb && \ - rm supabase_1.163.6_linux_arm64.deb; \ - fi && \ - rm -rf /var/lib/apt/lists/* - -# TODO(@aminediro) : multistage build. Probably dont neet poetry once its built -# Install Poetry -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false && \ - poetry config virtualenvs.in-project false - -# Add Rust binaries to the PATH -ENV PATH="/root/.cargo/bin:${PATH}" \ - POETRY_CACHE_DIR=/tmp/poetry_cache \ - PYTHONDONTWRITEBYTECODE=1 \ - POETRY_VIRTUALENVS_PATH=/code/api/.venv-docker - -WORKDIR /code/api -COPY api/pyproject.toml api/poetry.lock api/README.md /code/api/ -COPY api/quivr_api /code/api/quivr_api - -# Run install -# Run install -RUN poetry install --no-directory --no-root --with dev && rm -rf $POETRY_CACHE_DIR - -ENV PYTHONPATH=/code/api - -EXPOSE 5050 diff --git a/backend/api/README.md b/backend/api/README.md deleted file mode 100644 index 35110b517..000000000 --- a/backend/api/README.md +++ /dev/null @@ -1 +0,0 @@ -# quivr-api 0.1 diff --git a/backend/api/pyproject.toml b/backend/api/pyproject.toml deleted file mode 100644 index f8873a15a..000000000 --- a/backend/api/pyproject.toml +++ /dev/null @@ -1,65 +0,0 @@ -[project] -name = "quivr-api" -version = "0.1.0" -description = "quivr backend API" -authors = [{ name = "Stan Girard", email = "stan@quivr.app" }] -dependencies = [ - "quivr-core", - "supabase>=2.0.0", - "fastapi>=0.100.0", - "uvloop>=0.18.0", - "python-jose>=3.0.0", - "python-multipart>=0.0.9", - "uvicorn>=0.25.0", - "redis>=5.0.0", - "asyncpg>=0.29.0", - "psycopg2-binary>=2.9.9", - "sqlmodel>=0.0.21", - "celery[redis]>=5.4.0", - "pydantic-settings>=2.4.0", - "python-dotenv>=1.0.1", - "unidecode>=1.3.8", - "colorlog>=6.8.2", - "posthog>=3.5.0", - "pyinstrument>=4.7.2", - "sentry-sdk[fastapi]>=2.13.0", - "google-api-python-client>=2.141.0", - "google-auth-httplib2>=0.2.0", - "google-auth-oauthlib>=1.2.1", - "dropbox>=12.0.2", - "msal>=1.30.0", - "notion-client>=2.2.1", - "markdownify>=0.13.1", - "langchain-openai>=0.1.21", - "resend>=2.4.0", - "langchain>=0.2.14,<0.3.0", - "litellm>=1.43.15", - "openai>=1.40.8", - "tiktoken>=0.7.0", - "langchain-community>=0.2.12", - "langchain-cohere>=0.2.2", - "llama-parse>=0.4.9", - "pgvector>=0.3.2", -] -readme = "README.md" -requires-python = "< 3.12" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.rye] -managed = true -dev-dependencies = [] -universal = true - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["quivr_api"] - - -[[tool.rye.sources]] -name = "quivr-core" -path = "../quivr-core" diff --git a/backend/api/quivr_api/__init__.py b/backend/api/quivr_api/__init__.py deleted file mode 100644 index f25c4b4b9..000000000 --- a/backend/api/quivr_api/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB - -from .modules.chat.entity.chat import Chat, ChatHistory -from .modules.sync.entity.sync_models import NotionSyncFile -from .modules.user.entity.user_identity import User - -__all__ = [ - "Chat", - "ChatHistory", - "User", - "NotionSyncFile", - "KnowledgeDB", - "Brain", -] diff --git a/backend/api/quivr_api/celery_config.py b/backend/api/quivr_api/celery_config.py deleted file mode 100644 index b39b14bb7..000000000 --- a/backend/api/quivr_api/celery_config.py +++ /dev/null @@ -1,28 +0,0 @@ -# celery_config.py -import os - -import dotenv -from celery import Celery - -dotenv.load_dotenv() - -CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "") -CELERY_BROKER_QUEUE_NAME = os.getenv("CELERY_BROKER_QUEUE_NAME", "quivr") - -celery = Celery(__name__) - -if CELERY_BROKER_URL.startswith("redis"): - celery = Celery( - __name__, - broker=f"{CELERY_BROKER_URL}", - backend=f"{CELERY_BROKER_URL}", - task_concurrency=4, - worker_prefetch_multiplier=2, - task_serializer="json", - result_extended=True, - task_send_sent_event=True, - ) -else: - raise ValueError(f"Unsupported broker URL: {CELERY_BROKER_URL}") - -celery.autodiscover_tasks(["quivr_api.modules.chat"]) diff --git a/backend/api/quivr_api/logger.py b/backend/api/quivr_api/logger.py deleted file mode 100644 index 5b12765c1..000000000 --- a/backend/api/quivr_api/logger.py +++ /dev/null @@ -1,247 +0,0 @@ -import logging -import os -import queue -import sys -import threading -from logging.handlers import RotatingFileHandler -from typing import List - -import orjson -import requests -import structlog - -from quivr_api.models.settings import parseable_settings - -# Thread-safe queue for log messages -log_queue = queue.Queue() -stop_log_queue = threading.Event() - - -class ParseableLogHandler(logging.Handler): - def __init__( - self, - base_parseable_url: str, - auth_token: str, - stream_name: str, - batch_size: int = 10, - flush_interval: float = 1, - ): - super().__init__() - self.base_url = base_parseable_url - self.stream_name = stream_name - self.url = self.base_url + self.stream_name - self.batch_size = batch_size - self.flush_interval = flush_interval - self._worker_thread = threading.Thread(target=self._process_log_queue) - self._worker_thread.daemon = True - self._worker_thread.start() - self.headers = { - "Authorization": f"Basic {auth_token}", # base64 encoding user:mdp - "Content-Type": "application/json", - } - - def emit(self, record: logging.LogRecord): - # FIXME (@AmineDiro): This ping-pong of serialization/deserialization is a limitation of logging formatter - # The formatter should return a 'str' for the logger to print - if isinstance(record.msg, str): - return - elif isinstance(record.msg, dict): - logger_name = record.msg.get("logger", None) - if logger_name and ( - logger_name.startswith("quivr_api.access") - or logger_name.startswith("quivr_api.error") - ): - url = record.msg.get("url", None) - # Filter on healthz - if url and "healthz" not in url: - fmt = orjson.loads(self.format(record)) - log_queue.put(fmt) - else: - return - - def _process_log_queue(self): - """Background thread that processes the log queue and sends logs to Parseable.""" - logs_batch = [] - while not stop_log_queue.is_set(): - try: - # Collect logs for batch processing - log_data = log_queue.get(timeout=self.flush_interval) - logs_batch.append(log_data) - - # Send logs if batch size is reached - if len(logs_batch) >= self.batch_size: - self._send_logs_to_parseable(logs_batch) - logs_batch.clear() - - except queue.Empty: - # If the queue is empty, send any remaining logs - if logs_batch: - self._send_logs_to_parseable(logs_batch) - logs_batch.clear() - - def _send_logs_to_parseable(self, logs: List[str]): - payload = orjson.dumps(logs) - try: - response = requests.post(self.url, headers=self.headers, data=payload) - if response.status_code != 200: - print(f"Failed to send logs to Parseable server: {response.text}") - except Exception as e: - print(f"Error sending logs to Parseable: {e}") - - def stop(self): - """Stop the background worker thread and process any remaining logs.""" - stop_log_queue.set() - self._worker_thread.join() - # Process remaining logs before shutting down - remaining_logs = list(log_queue.queue) - if remaining_logs: - self._send_logs_to_parseable(remaining_logs) - - -def extract_from_record(_, __, event_dict): - """ - Extract thread and process names and add them to the event dict. - """ - record = event_dict["_record"] - event_dict["thread_name"] = record.threadName - event_dict["process_name"] = record.processName - return event_dict - - -def drop_http_context(_, __, event_dict): - """ - Extract thread and process names and add them to the event dict. - """ - keys = ["msg", "logger", "level", "timestamp", "exc_info"] - return {k: event_dict.get(k, None) for k in keys} - - -def setup_logger( - log_file="application.log", send_log_server: bool = parseable_settings.use_parseable -): - structlog.reset_defaults() - # Shared handlers - shared_processors = [ - structlog.contextvars.merge_contextvars, - structlog.stdlib.add_log_level, - structlog.stdlib.add_logger_name, - structlog.stdlib.PositionalArgumentsFormatter(), - structlog.processors.TimeStamper(fmt="iso"), - structlog.processors.StackInfoRenderer(), - structlog.processors.UnicodeDecoder(), - structlog.processors.EventRenamer("msg"), - ] - structlog.configure( - processors=shared_processors - + [ - structlog.stdlib.ProcessorFormatter.wrap_for_formatter, - ], - # Use standard logging compatible logger - logger_factory=structlog.stdlib.LoggerFactory(), - wrapper_class=structlog.stdlib.BoundLogger, - # Use Python's logging configuration - cache_logger_on_first_use=True, - ) - # Set Formatters - plain_fmt = structlog.stdlib.ProcessorFormatter( - foreign_pre_chain=shared_processors, - processors=[ - extract_from_record, - structlog.processors.format_exc_info, - structlog.stdlib.ProcessorFormatter.remove_processors_meta, - structlog.dev.ConsoleRenderer( - colors=False, exception_formatter=structlog.dev.plain_traceback - ), - ], - ) - color_fmt = structlog.stdlib.ProcessorFormatter( - processors=[ - drop_http_context, - structlog.dev.ConsoleRenderer( - colors=True, - exception_formatter=structlog.dev.RichTracebackFormatter( - show_locals=False - ), - ), - ], - foreign_pre_chain=shared_processors, - ) - parseable_fmt = structlog.stdlib.ProcessorFormatter( - processors=[ - # TODO: Which one gets us the better debug experience ? - # structlog.processors.ExceptionRenderer( - # exception_formatter=structlog.tracebacks.ExceptionDictTransformer( - # show_locals=False - # ) - # ), - structlog.processors.format_exc_info, - structlog.stdlib.ProcessorFormatter.remove_processors_meta, - structlog.processors.JSONRenderer(), - ], - foreign_pre_chain=shared_processors - + [ - structlog.processors.CallsiteParameterAdder( - { - structlog.processors.CallsiteParameter.FUNC_NAME, - structlog.processors.CallsiteParameter.LINENO, - } - ), - ], - ) - - # Set handlers - console_handler = logging.StreamHandler(sys.stdout) - file_handler = RotatingFileHandler( - log_file, maxBytes=5000000, backupCount=5 - ) # 5MB file - console_handler.setFormatter(color_fmt) - file_handler.setFormatter(plain_fmt) - handlers: list[logging.Handler] = [console_handler, file_handler] - if ( - send_log_server - and parseable_settings.parseable_url is not None - and parseable_settings.parseable_auth is not None - and parseable_settings.parseable_stream_name - ): - parseable_handler = ParseableLogHandler( - auth_token=parseable_settings.parseable_auth, - base_parseable_url=parseable_settings.parseable_url, - stream_name=parseable_settings.parseable_stream_name, - ) - parseable_handler.setFormatter(parseable_fmt) - handlers.append(parseable_handler) - - # Configure logger - log_level = os.getenv("LOG_LEVEL", "INFO").upper() - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - root_logger.handlers = [] - for handler in handlers: - root_logger.addHandler(handler) - - _clear_uvicorn_logger() - - -def _clear_uvicorn_logger(): - for _log in [ - "uvicorn", - "httpcore", - "uvicorn.error", - "uvicorn.access", - "urllib3", - "httpx", - ]: - # Clear the log handlers for uvicorn loggers, and enable propagation - # so the messages are caught by our root logger and formatted correctly - # by structlog - logging.getLogger(_log).setLevel(logging.WARNING) - logging.getLogger(_log).handlers.clear() - logging.getLogger(_log).propagate = True - - -setup_logger() - - -def get_logger(name: str | None = None): - assert structlog.is_configured() - return structlog.get_logger(name) diff --git a/backend/api/quivr_api/main.py b/backend/api/quivr_api/main.py deleted file mode 100644 index 5d2696051..000000000 --- a/backend/api/quivr_api/main.py +++ /dev/null @@ -1,122 +0,0 @@ -import logging -import os - -import sentry_sdk -from dotenv import load_dotenv # type: ignore -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from pyinstrument import Profiler -from sentry_sdk.integrations.fastapi import FastApiIntegration -from sentry_sdk.integrations.starlette import StarletteIntegration - -from quivr_api.logger import get_logger, stop_log_queue -from quivr_api.middlewares.cors import add_cors_middleware -from quivr_api.middlewares.logging_middleware import LoggingMiddleware -from quivr_api.modules.analytics.controller.analytics_routes import analytics_router -from quivr_api.modules.api_key.controller import api_key_router -from quivr_api.modules.assistant.controller import assistant_router -from quivr_api.modules.brain.controller import brain_router -from quivr_api.modules.chat.controller import chat_router -from quivr_api.modules.knowledge.controller import knowledge_router -from quivr_api.modules.misc.controller import misc_router -from quivr_api.modules.models.controller.model_routes import model_router -from quivr_api.modules.onboarding.controller import onboarding_router -from quivr_api.modules.prompt.controller import prompt_router -from quivr_api.modules.sync.controller import sync_router -from quivr_api.modules.upload.controller import upload_router -from quivr_api.modules.user.controller import user_router -from quivr_api.routes.crawl_routes import crawl_router -from quivr_api.routes.subscription_routes import subscription_router -from quivr_api.utils.telemetry import maybe_send_telemetry - -load_dotenv() - - -logging.basicConfig(level=logging.INFO) -logger = get_logger("quivr_api") - - -def before_send(event, hint): - # If this is a transaction event - if event["type"] == "transaction": - # And the transaction name contains 'healthz' - if "healthz" in event["transaction"]: - # Drop the event by returning None - return None - # For other events, return them as is - return event - - -sentry_dsn = os.getenv("SENTRY_DSN") -if sentry_dsn: - sentry_sdk.init( - dsn=sentry_dsn, - sample_rate=0.1, - enable_tracing=True, - traces_sample_rate=0.1, - integrations=[ - StarletteIntegration(transaction_style="url"), - FastApiIntegration(transaction_style="url"), - ], - before_send=before_send, - ) - -app = FastAPI() -add_cors_middleware(app) - -app.add_middleware(LoggingMiddleware) - - -app.include_router(brain_router) -app.include_router(chat_router) -app.include_router(crawl_router) -app.include_router(assistant_router) -app.include_router(sync_router) -app.include_router(onboarding_router) -app.include_router(misc_router) -app.include_router(analytics_router) -app.include_router(upload_router) -app.include_router(user_router) -app.include_router(api_key_router) -app.include_router(subscription_router) -app.include_router(prompt_router) -app.include_router(knowledge_router) -app.include_router(model_router) - -PROFILING = os.getenv("PROFILING", "false").lower() == "true" - - -if PROFILING: - - @app.middleware("http") - async def profile_request(request: Request, call_next): - profiling = request.query_params.get("profile", False) - if profiling: - profiler = Profiler() - profiler.start() - await call_next(request) - profiler.stop() - return HTMLResponse(profiler.output_html()) - else: - return await call_next(request) - - -@app.on_event("shutdown") -def shutdown_event(): - stop_log_queue.set() - - -if os.getenv("TELEMETRY_ENABLED") == "true": - logger.info("Telemetry enabled, we use telemetry to collect anonymous usage data.") - logger.info( - "To disable telemetry, set the TELEMETRY_ENABLED environment variable to false." - ) - maybe_send_telemetry("booting", {"status": "ok"}) - maybe_send_telemetry("ping", {"ping": "pong"}) - - -if __name__ == "__main__": - # run main.py to debug backend - import uvicorn - - uvicorn.run(app, host="0.0.0.0", port=5050, log_level="debug", access_log=False) diff --git a/backend/api/quivr_api/middlewares/auth/__init__.py b/backend/api/quivr_api/middlewares/auth/__init__.py deleted file mode 100644 index 05f13aa37..000000000 --- a/backend/api/quivr_api/middlewares/auth/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .auth_bearer import AuthBearer, get_current_user - -__all__ = [ - "AuthBearer", - "get_current_user", -] diff --git a/backend/api/quivr_api/middlewares/auth/auth_bearer.py b/backend/api/quivr_api/middlewares/auth/auth_bearer.py deleted file mode 100644 index a5ce9af9c..000000000 --- a/backend/api/quivr_api/middlewares/auth/auth_bearer.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -from typing import Optional - -import structlog -from fastapi import Depends, HTTPException, Request -from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer - -from quivr_api.middlewares.auth.jwt_token_handler import ( - decode_access_token, - verify_token, -) -from quivr_api.modules.api_key.service.api_key_service import ApiKeyService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -api_key_service = ApiKeyService() - -logger = structlog.stdlib.get_logger("quivr_api.access") - - -class AuthBearer(HTTPBearer): - def __init__(self, auto_error: bool = True): - super().__init__(auto_error=auto_error) - - async def __call__( - self, - request: Request, - ): - credentials: Optional[HTTPAuthorizationCredentials] = await super().__call__( - request - ) - self.check_scheme(credentials) - token = credentials.credentials # pyright: ignore reportPrivateUsage=none - return await self.authenticate( - token, - ) - - def check_scheme(self, credentials): - if credentials and credentials.scheme != "Bearer": - raise HTTPException(status_code=401, detail="Token must be Bearer") - elif not credentials: - raise HTTPException( - status_code=403, detail="Authentication credentials missing" - ) - - async def authenticate( - self, - token: str, - ) -> UserIdentity: - if os.environ.get("AUTHENTICATE") == "false": - return self.get_test_user() - elif verify_token(token): - return decode_access_token(token) - elif await api_key_service.verify_api_key( - token, - ): - return await api_key_service.get_user_from_api_key( - token, - ) - else: - raise HTTPException(status_code=401, detail="Invalid token or api key.") - - def get_test_user(self) -> UserIdentity: - return UserIdentity( - email="admin@quivr.app", - id="39418e3b-0258-4452-af60-7acfcc1263ff", # type: ignore - ) # replace with test user information - - -auth_bearer = AuthBearer() - - -async def get_current_user(user: UserIdentity = Depends(auth_bearer)) -> UserIdentity: - # Due to context switch in FastAPI executor we can't get this id back - # We log it as an additional log so we can get information if exception was raised - # https://www.structlog.org/en/stable/contextvars.html - structlog.contextvars.bind_contextvars(client_id=str(user.id)) - logger.info("Authentication success") - return user diff --git a/backend/api/quivr_api/middlewares/auth/jwt_token_handler.py b/backend/api/quivr_api/middlewares/auth/jwt_token_handler.py deleted file mode 100644 index e6438d2ac..000000000 --- a/backend/api/quivr_api/middlewares/auth/jwt_token_handler.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -from datetime import datetime, timedelta -from typing import Optional - -from jose import jwt -from jose.exceptions import JWTError - -from quivr_api.modules.user.entity.user_identity import UserIdentity - -SECRET_KEY = os.environ.get("JWT_SECRET_KEY") -ALGORITHM = "HS256" - -if not SECRET_KEY: - raise ValueError("JWT_SECRET_KEY environment variable not set") - - -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -def decode_access_token(token: str) -> UserIdentity: - try: - payload = jwt.decode( - token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_aud": False} - ) - except JWTError: - return None # pyright: ignore reportPrivateUsage=none - - return UserIdentity( - email=payload.get("email"), - id=payload.get("sub"), # pyright: ignore reportPrivateUsage=none - ) - - -def verify_token(token: str): - payload = decode_access_token(token) - return payload is not None diff --git a/backend/api/quivr_api/middlewares/cors.py b/backend/api/quivr_api/middlewares/cors.py deleted file mode 100644 index b68fc324d..000000000 --- a/backend/api/quivr_api/middlewares/cors.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi.middleware.cors import CORSMiddleware - -origins = [ - "http://localhost", - "http://localhost:3000", - "http://localhost:3001", - "https://quivr.app", - "https://www.quivr.app", - "http://quivr.app", - "http://www.quivr.app", - "https://chat.quivr.app", - "*", -] - - -def add_cors_middleware(app): - app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) diff --git a/backend/api/quivr_api/middlewares/logging_middleware.py b/backend/api/quivr_api/middlewares/logging_middleware.py deleted file mode 100644 index b9911f30d..000000000 --- a/backend/api/quivr_api/middlewares/logging_middleware.py +++ /dev/null @@ -1,95 +0,0 @@ -import os -import time -import uuid - -import structlog -from fastapi import Request, Response, status -from starlette.middleware.base import BaseHTTPMiddleware -from structlog.contextvars import ( - bind_contextvars, - clear_contextvars, -) - -logger = structlog.stdlib.get_logger("quivr_api.access") - - -git_sha = os.getenv("PORTER_IMAGE_TAG", None) - - -def clean_dict(d): - """Remove None values from a dictionary.""" - return {k: v for k, v in d.items() if v is not None} - - -class LoggingMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - clear_contextvars() - # Generate a unique request ID - request_id = str(uuid.uuid4()) - - client_addr = ( - f"{request.client.host}:{request.client.port}" if request.client else None - ) - url = request.url.path - http_version = request.scope["http_version"] - - bind_contextvars( - **clean_dict( - { - "git_head": git_sha, - "request_id": request_id, - "method": request.method, - "query_params": dict(request.query_params), - "client_addr": client_addr, - "request_user_agent": request.headers.get("user-agent"), - "request_content_type": request.headers.get("content-type"), - "url": url, - "http_version": http_version, - } - ) - ) - - # Start time - start_time = time.perf_counter() - response = Response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) - try: - # Process the request - response: Response = await call_next(request) - process_time = time.perf_counter() - start_time - bind_contextvars( - **clean_dict( - { - "response_content_type": response.headers.get("content-type"), - "response_status": response.status_code, - "response_headers": dict(response.headers), - "timing_request_total_ms": round(process_time * 1e3, 3), - } - ) - ) - - logger.info( - f"""{client_addr} - "{request.method} {url} HTTP/{http_version}" {response.status_code}""", - ) - except Exception: - process_time = time.perf_counter() - start_time - bind_contextvars( - **clean_dict( - { - "response_status": response.status_code, - "timing_request_total_ms": round(process_time * 1000, 3), - } - ) - ) - structlog.stdlib.get_logger("quivr_api.error").exception( - "Request failed with exception" - ) - raise - - finally: - clear_contextvars() - - # Add X-Request-ID to response headers - response.headers["X-Request-ID"] = request_id - response.headers["X-Process-Time"] = str(process_time) - - return response diff --git a/backend/api/quivr_api/models/brains_subscription_invitations.py b/backend/api/quivr_api/models/brains_subscription_invitations.py deleted file mode 100644 index fc0ddf048..000000000 --- a/backend/api/quivr_api/models/brains_subscription_invitations.py +++ /dev/null @@ -1,14 +0,0 @@ -from uuid import UUID - -from pydantic import BaseModel, ConfigDict - -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - - -class BrainSubscription(BaseModel): - brain_id: UUID - email: str - rights: str = "Viewer" - model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/backend/api/quivr_api/models/crawler.py b/backend/api/quivr_api/models/crawler.py deleted file mode 100644 index 73fdb664e..000000000 --- a/backend/api/quivr_api/models/crawler.py +++ /dev/null @@ -1,11 +0,0 @@ -import os - -from pydantic import BaseModel - - -class CrawlWebsite(BaseModel): - url: str - js: bool = False - depth: int = int(os.getenv("CRAWL_DEPTH", "1")) - max_pages: int = 100 - max_time: int = 60 diff --git a/backend/api/quivr_api/models/databases/llm_models.py b/backend/api/quivr_api/models/databases/llm_models.py deleted file mode 100644 index 00722817c..000000000 --- a/backend/api/quivr_api/models/databases/llm_models.py +++ /dev/null @@ -1,13 +0,0 @@ -from pydantic import BaseModel - - -class LLMModel(BaseModel): - """LLM models stored in the database that are allowed to be used by the users. - Args: - BaseModel (BaseModel): Pydantic BaseModel - """ - - name: str = "gpt-3.5-turbo-0125" - price: int = 1 - max_input: int = 512 - max_output: int = 512 diff --git a/backend/api/quivr_api/models/databases/repository.py b/backend/api/quivr_api/models/databases/repository.py deleted file mode 100644 index 1b19d64ff..000000000 --- a/backend/api/quivr_api/models/databases/repository.py +++ /dev/null @@ -1,85 +0,0 @@ -from abc import ABC, abstractmethod -from datetime import datetime -from uuid import UUID - -from .llm_models import LLMModel - - -class Repository(ABC): - @abstractmethod - def create_user_daily_usage(self, user_id: UUID, user_email: str, date: datetime): - pass - - @abstractmethod - def get_user_usage(self, user_id: UUID): - pass - - @abstractmethod - def get_models(self) -> LLMModel | None: - pass - - @abstractmethod - def get_user_requests_count_for_month(self, user_id: UUID, date: datetime): - pass - - @abstractmethod - def update_user_request_count(self, user_id: UUID, date: str): - pass - - @abstractmethod - def increment_user_request_count( - self, user_id: UUID, date: str, current_request_count - ): - pass - - @abstractmethod - def set_file_vectors_ids(self, file_sha1: str): - pass - - @abstractmethod - def get_brain_vectors_by_brain_id_and_file_sha1( - self, brain_id: UUID, file_sha1: str - ): - pass - - @abstractmethod - def create_subscription_invitation( - self, brain_id: UUID, user_email: str, rights: str - ): - pass - - @abstractmethod - def update_subscription_invitation( - self, brain_id: UUID, user_email: str, rights: str - ): - pass - - @abstractmethod - def get_subscription_invitations_by_brain_id_and_email( - self, brain_id: UUID, user_email: str - ): - pass - - @abstractmethod - def get_vectors_by_file_name(self, file_name: str): - pass - - @abstractmethod - def similarity_search(self, query_embedding, table: str, k: int, threshold: float): - pass - - @abstractmethod - def update_summary(self, document_id: UUID, summary_id: int): - pass - - @abstractmethod - def get_vectors_by_batch(self, batch_id: UUID): - pass - - @abstractmethod - def get_vectors_in_batch(self, batch_ids): - pass - - @abstractmethod - def get_vectors_by_file_sha1(self, file_sha1): - pass diff --git a/backend/api/quivr_api/models/databases/supabase/__init__.py b/backend/api/quivr_api/models/databases/supabase/__init__.py deleted file mode 100644 index 4efd87c4e..000000000 --- a/backend/api/quivr_api/models/databases/supabase/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from quivr_api.models.databases.supabase.brains_subscription_invitations import ( - BrainSubscription, -) -from quivr_api.models.databases.supabase.files import File -from quivr_api.models.databases.supabase.user_usage import UserUsage -from quivr_api.models.databases.supabase.vectors import Vector - -__all__ = ["BrainSubscription", "File", "UserUsage", "Vector"] diff --git a/backend/api/quivr_api/models/databases/supabase/brains_subscription_invitations.py b/backend/api/quivr_api/models/databases/supabase/brains_subscription_invitations.py deleted file mode 100644 index e2e58c5d3..000000000 --- a/backend/api/quivr_api/models/databases/supabase/brains_subscription_invitations.py +++ /dev/null @@ -1,40 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.models.databases.repository import Repository - -logger = get_logger(__name__) - - -class BrainSubscription(Repository): - def __init__(self, supabase_client): - self.db = supabase_client - - def create_subscription_invitation(self, brain_id, user_email, rights): - logger.info("Creating subscription invitation") - response = ( - self.db.table("brain_subscription_invitations") - .insert({"brain_id": str(brain_id), "email": user_email, "rights": rights}) - .execute() - ) - return response.data - - def update_subscription_invitation(self, brain_id, user_email, rights): - logger.info("Updating subscription invitation") - response = ( - self.db.table("brain_subscription_invitations") - .update({"rights": rights}) - .eq("brain_id", str(brain_id)) - .eq("email", user_email) - .execute() - ) - return response.data - - def get_subscription_invitations_by_brain_id_and_email(self, brain_id, user_email): - response = ( - self.db.table("brain_subscription_invitations") - .select("*") - .eq("brain_id", str(brain_id)) - .eq("email", user_email) - .execute() - ) - - return response diff --git a/backend/api/quivr_api/models/databases/supabase/files.py b/backend/api/quivr_api/models/databases/supabase/files.py deleted file mode 100644 index df1704eab..000000000 --- a/backend/api/quivr_api/models/databases/supabase/files.py +++ /dev/null @@ -1,28 +0,0 @@ -from quivr_api.models.databases.repository import Repository - - -class File(Repository): - def __init__(self, supabase_client): - self.db = supabase_client - - def set_file_vectors_ids(self, file_sha1): - response = ( - self.db.table("vectors") - .select("id") - .filter("file_sha1", "eq", file_sha1) - .execute() - ) - return response.data - - def get_brain_vectors_by_brain_id_and_file_sha1(self, brain_id, file_sha1): - self.set_file_vectors_ids(file_sha1) - # Check if file exists in that brain - response = ( - self.db.table("brains_vectors") - .select("brain_id, vector_id") - .filter("brain_id", "eq", str(brain_id)) - .filter("file_sha1", "eq", file_sha1) - .execute() - ) - - return response diff --git a/backend/api/quivr_api/models/databases/supabase/supabase.py b/backend/api/quivr_api/models/databases/supabase/supabase.py deleted file mode 100644 index ac1d27f7a..000000000 --- a/backend/api/quivr_api/models/databases/supabase/supabase.py +++ /dev/null @@ -1,21 +0,0 @@ -from quivr_api.models.databases.supabase import ( - BrainSubscription, - File, - UserUsage, - Vector, -) - - -# TODO: REMOVE THIS CLASS ! -class SupabaseDB( - UserUsage, - File, - BrainSubscription, - Vector, -): - def __init__(self, supabase_client): - self.db = supabase_client - UserUsage.__init__(self, supabase_client) - File.__init__(self, supabase_client) - BrainSubscription.__init__(self, supabase_client) - Vector.__init__(self, supabase_client) diff --git a/backend/api/quivr_api/models/databases/supabase/user_usage.py b/backend/api/quivr_api/models/databases/supabase/user_usage.py deleted file mode 100644 index 327868867..000000000 --- a/backend/api/quivr_api/models/databases/supabase/user_usage.py +++ /dev/null @@ -1,128 +0,0 @@ -from datetime import datetime, timedelta -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.models.databases.repository import Repository - -logger = get_logger(__name__) - - -# TODO: change the name of this class because another one already exists -class UserUsage(Repository): - def __init__(self, supabase_client): - self.db = supabase_client - - def create_user_daily_usage( - self, user_id: UUID, user_email: str, date: datetime, number: int = 1 - ): - return ( - self.db.table("user_daily_usage") - .insert( - { - "user_id": str(user_id), - "email": user_email, - "date": date, - "daily_requests_count": number, - } - ) - .execute() - ) - - def get_user_settings(self, user_id): - """ - Fetch the user settings from the database - """ - - user_settings_response = ( - self.db.from_("user_settings") - .select("*") - .filter("user_id", "eq", str(user_id)) - .execute() - ).data - - if len(user_settings_response) == 0: - # Create the user settings - user_settings_response = ( - self.db.table("user_settings") - .insert({"user_id": str(user_id)}) - .execute() - ).data - - if len(user_settings_response) == 0: - raise ValueError("User settings could not be created") - - user_settings = user_settings_response[0] - - return user_settings - - def get_models(self): - model_settings_response = (self.db.from_("models").select("*").execute()).data - if len(model_settings_response) == 0: - raise ValueError("An issue occured while fetching the model settings") - return model_settings_response - - def get_user_monthly(self, user_id): - pass - - def get_user_usage(self, user_id): - """ - Fetch the user request stats from the database - """ - requests_stats = ( - self.db.from_("user_daily_usage") - .select("*") - .filter("user_id", "eq", user_id) - .execute() - ) - return requests_stats.data - - def get_user_requests_count_for_day(self, user_id, date): - """ - Fetch the user request count from the database - """ - response = ( - self.db.from_("user_daily_usage") - .select("daily_requests_count") - .filter("user_id", "eq", user_id) - .filter("date", "eq", date) - .execute() - ).data - - if response and len(response) > 0: - return response[0]["daily_requests_count"] - return 0 - - def get_user_requests_count_for_month(self, user_id, date): - """ - Fetch the user request count from the database - """ - date_30_days_ago = (datetime.now() - timedelta(days=30)).strftime("%Y%m%d") - - response = ( - self.db.from_("user_daily_usage") - .select("daily_requests_count") - .filter("user_id", "eq", user_id) - .filter("date", "gte", date_30_days_ago) - .execute() - ).data - - if response and len(response) > 0: - return sum(row["daily_requests_count"] for row in response) - return 0 - - def increment_user_request_count(self, user_id, date, number: int = 1): - """ - Increment the user's requests count for a specific day - """ - - self.update_user_request_count(user_id, daily_requests_count=number, date=date) - - def update_user_request_count(self, user_id, daily_requests_count, date): - response = ( - self.db.table("user_daily_usage") - .update({"daily_requests_count": daily_requests_count}) - .match({"user_id": user_id, "date": date}) - .execute() - ) - - return response diff --git a/backend/api/quivr_api/models/databases/supabase/vectors.py b/backend/api/quivr_api/models/databases/supabase/vectors.py deleted file mode 100644 index 145b90ea5..000000000 --- a/backend/api/quivr_api/models/databases/supabase/vectors.py +++ /dev/null @@ -1,76 +0,0 @@ -from quivr_api.models.databases.repository import Repository - - -class Vector(Repository): - def __init__(self, supabase_client): - self.db = supabase_client - - def get_vectors_by_file_name(self, file_name): - response = ( - self.db.table("vectors") - .select( - "metadata->>file_name, metadata->>file_size, metadata->>file_extension, metadata->>file_url", - "content", - "brains_vectors(brain_id,vector_id)", - ) - .match({"metadata->>file_name": file_name}) - .execute() - ) - - return response - - def get_vectors_by_file_sha1(self, file_sha1): - response = ( - self.db.table("vectors") - .select("id") - .filter("file_sha1", "eq", file_sha1) - .execute() - ) - - return response - - # TODO: remove duplicate similarity_search in supabase vector store - def similarity_search(self, query_embedding, table, k, threshold): - response = self.db.rpc( - table, - { - "query_embedding": query_embedding, - "match_count": k, - "match_threshold": threshold, - }, - ).execute() - return response - - def update_summary(self, document_id, summary_id): - return ( - self.db.table("summaries") - .update({"document_id": document_id}) - .match({"id": summary_id}) - .execute() - ) - - def get_vectors_by_batch(self, batch_id): - response = ( - self.db.table("vectors") - .select( - "name:metadata->>file_name, size:metadata->>file_size", - count="exact", - ) - .eq("id", batch_id) - .execute() - ) - - return response - - def get_vectors_in_batch(self, batch_ids): - response = ( - self.db.table("vectors") - .select( - "name:metadata->>file_name, size:metadata->>file_size", - count="exact", - ) - .in_("id", batch_ids) - .execute() - ) - - return response diff --git a/backend/api/quivr_api/models/settings.py b/backend/api/quivr_api/models/settings.py deleted file mode 100644 index 5f4050a99..000000000 --- a/backend/api/quivr_api/models/settings.py +++ /dev/null @@ -1,138 +0,0 @@ -from uuid import UUID - -from posthog import Posthog -from pydantic_settings import BaseSettings, SettingsConfigDict - - -class BrainRateLimiting(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - max_brain_per_user: int = 5 - - -class SendEmailSettings(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - resend_contact_sales_from: str = "null" - resend_contact_sales_to: str = "null" - - -# The `PostHogSettings` class is used to initialize and interact with the PostHog analytics service. -class PostHogSettings(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - posthog_api_key: str | None = None - posthog_api_url: str | None = None - posthog: Posthog | None = None - - def __init__(self, *args, **kwargs): - """ - The function initializes the "posthog" attribute and calls the "initialize_posthog" method. - """ - super().__init__(*args, **kwargs) - self.posthog = None - self.initialize_posthog() - - def initialize_posthog(self): - """ - The function initializes a PostHog client with an API key and URL. - """ - if self.posthog_api_key and self.posthog_api_url: - self.posthog = Posthog( - api_key=self.posthog_api_key, host=self.posthog_api_url - ) - - def log_event(self, user_id: UUID, event_name: str, event_properties: dict): - """ - The function logs an event with a user ID, event name, and event properties using the PostHog - analytics tool. - - :param user_id: The user_id parameter is a UUID (Universally Unique Identifier) that uniquely - identifies a user. It is typically used to track and identify individual users in an application - or system - :type user_id: UUID - :param event_name: The event_name parameter is a string that represents the name or type of the - event that you want to log. It could be something like "user_signed_up", "item_purchased", or - "page_viewed" - :type event_name: str - :param event_properties: The event_properties parameter is a dictionary that contains additional - information or properties related to the event being logged. These properties provide more - context or details about the event and can be used for analysis or filtering purposes - :type event_properties: dict - """ - if self.posthog: - self.posthog.capture(user_id, event_name, event_properties) - - def set_user_properties(self, user_id: UUID, event_name, properties: dict): - """ - The function sets user properties for a given user ID and event name using the PostHog analytics - tool. - - :param user_id: The user_id parameter is a UUID (Universally Unique Identifier) that uniquely - identifies a user. It is used to associate the user with the event and properties being captured - :type user_id: UUID - :param event_name: The `event_name` parameter is a string that represents the name of the event - that you want to capture. It could be something like "user_signed_up" or "item_purchased" - :param properties: The `properties` parameter is a dictionary that contains the user properties - that you want to set. Each key-value pair in the dictionary represents a user property, where - the key is the name of the property and the value is the value you want to set for that property - :type properties: dict - """ - if self.posthog: - self.posthog.capture( - user_id, event=event_name, properties={"$set": properties} - ) - - def set_once_user_properties(self, user_id: UUID, event_name, properties: dict): - """ - The function sets user properties for a specific event, ensuring that the properties are only - set once. - - :param user_id: The user_id parameter is a UUID (Universally Unique Identifier) that uniquely - identifies a user - :type user_id: UUID - :param event_name: The `event_name` parameter is a string that represents the name of the event - that you want to capture. It could be something like "user_signed_up" or "item_purchased" - :param properties: The `properties` parameter is a dictionary that contains the user properties - that you want to set. Each key-value pair in the dictionary represents a user property, where - the key is the property name and the value is the property value - :type properties: dict - """ - if self.posthog: - self.posthog.capture( - user_id, event=event_name, properties={"$set_once": properties} - ) - - -class BrainSettings(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - openai_api_key: str = "" - azure_openai_embeddings_url: str = "" - supabase_url: str = "" - supabase_service_key: str = "" - resend_api_key: str = "null" - resend_email_address: str = "brain@mail.quivr.app" - ollama_api_base_url: str | None = None - langfuse_public_key: str | None = None - langfuse_secret_key: str | None = None - pg_database_url: str - pg_database_async_url: str - embedding_dim: int = 1536 - - -class ResendSettings(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - resend_api_key: str = "null" - quivr_smtp_server: str = "" - quivr_smtp_port: int = 587 - quivr_smtp_username: str = "" - quivr_smtp_password: str = "" - - -class ParseableSettings(BaseSettings): - model_config = SettingsConfigDict(validate_default=False) - use_parseable: bool = False - parseable_url: str | None = None - parseable_auth: str | None = None - parseable_stream_name: str | None = None - - -settings = BrainSettings() # type: ignore -parseable_settings = ParseableSettings() diff --git a/backend/api/quivr_api/models/sqlalchemy_repository.py b/backend/api/quivr_api/models/sqlalchemy_repository.py deleted file mode 100644 index 7b2951879..000000000 --- a/backend/api/quivr_api/models/sqlalchemy_repository.py +++ /dev/null @@ -1,73 +0,0 @@ -from datetime import datetime -from uuid import uuid4 - -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship - -Base = declarative_base() - - -class User(Base): - __tablename__ = "users" - - user_id = Column(String, primary_key=True) - email = Column(String) - date = Column(DateTime) - daily_requests_count = Column(Integer) - - -class Brain(Base): - __tablename__ = "brains" - - brain_id = Column(Integer, primary_key=True) - name = Column(String) - users = relationship("BrainUser", back_populates="brain") - vectors = relationship("BrainVector", back_populates="brain") - - -class BrainUser(Base): - __tablename__ = "brains_users" - - id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey("users.user_id")) - brain_id = Column(Integer, ForeignKey("brains.brain_id")) - rights = Column(String) - - user = relationship("User") - brain = relationship("Brain", back_populates="users") - - -class BrainVector(Base): - __tablename__ = "brains_vectors" - - vector_id = Column(String, primary_key=True, default=lambda: str(uuid4())) - brain_id = Column(Integer, ForeignKey("brains.brain_id")) - file_sha1 = Column(String) - - brain = relationship("Brain", back_populates="vectors") - - -class BrainSubscriptionInvitation(Base): - __tablename__ = "brain_subscription_invitations" - - id = Column(Integer, primary_key=True) # Assuming an integer primary key named 'id' - brain_id = Column(String, ForeignKey("brains.brain_id")) - email = Column(String, ForeignKey("users.email")) - rights = Column(String) - - brain = relationship("Brain") - user = relationship("User", foreign_keys=[email]) - - -class ApiKey(Base): - __tablename__ = "api_keys" - - key_id = Column(String, primary_key=True, default=lambda: str(uuid4())) - user_id = Column(Integer, ForeignKey("users.user_id")) - api_key = Column(String, unique=True) - creation_time = Column(DateTime, default=datetime.utcnow) - is_active = Column(Boolean, default=True) - deleted_time = Column(DateTime, nullable=True) - - user = relationship("User") diff --git a/backend/api/quivr_api/modules/analytics/controller/analytics_routes.py b/backend/api/quivr_api/modules/analytics/controller/analytics_routes.py deleted file mode 100644 index 4dc924032..000000000 --- a/backend/api/quivr_api/modules/analytics/controller/analytics_routes.py +++ /dev/null @@ -1,25 +0,0 @@ -from uuid import UUID - -from fastapi import APIRouter, Depends, Query - -from quivr_api.middlewares.auth.auth_bearer import AuthBearer, get_current_user -from quivr_api.modules.analytics.entity.analytics import Range -from quivr_api.modules.analytics.service.analytics_service import AnalyticsService - -analytics_service = AnalyticsService() -analytics_router = APIRouter() - - -@analytics_router.get( - "/analytics/brains-usages", dependencies=[Depends(AuthBearer())], tags=["Analytics"] -) -async def get_brains_usages( - user: UUID = Depends(get_current_user), - brain_id: UUID = Query(None), - graph_range: Range = Query(Range.WEEK, alias="graph_range"), -): - """ - Get all user brains usages - """ - - return analytics_service.get_brains_usages(user.id, graph_range, brain_id) diff --git a/backend/api/quivr_api/modules/analytics/entity/analytics.py b/backend/api/quivr_api/modules/analytics/entity/analytics.py deleted file mode 100644 index 6d8dced41..000000000 --- a/backend/api/quivr_api/modules/analytics/entity/analytics.py +++ /dev/null @@ -1,20 +0,0 @@ -from datetime import date -from enum import IntEnum -from typing import List - -from pydantic import BaseModel - - -class Range(IntEnum): - WEEK = 7 - MONTH = 30 - QUARTER = 90 - - -class Usage(BaseModel): - date: date - usage_count: int - - -class BrainsUsages(BaseModel): - usages: List[Usage] diff --git a/backend/api/quivr_api/modules/analytics/repository/analytics.py b/backend/api/quivr_api/modules/analytics/repository/analytics.py deleted file mode 100644 index 959142298..000000000 --- a/backend/api/quivr_api/modules/analytics/repository/analytics.py +++ /dev/null @@ -1,56 +0,0 @@ -from collections import defaultdict -from datetime import datetime, timedelta -from typing import Optional -from uuid import UUID - -from quivr_api.modules.analytics.entity.analytics import BrainsUsages, Range, Usage -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.dependencies import get_supabase_client - -brain_user_service = BrainUserService() - - -class Analytics: - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - - def get_brains_usages( - self, user_id: UUID, graph_range: Range, brain_id: Optional[UUID] = None - ) -> BrainsUsages: - user_brains = brain_user_service.get_user_brains(user_id) - if brain_id is not None: - user_brains = [brain for brain in user_brains if brain.id == brain_id] - - usage_per_day = defaultdict(int) - - brain_ids = [brain.id for brain in user_brains] - chat_history = ( - self.db.from_("chat_history") - .select("*") - .in_("brain_id", brain_ids) - .execute() - ).data - - for chat in chat_history: - message_time = datetime.strptime( - chat["message_time"], "%Y-%m-%dT%H:%M:%S.%f" - ) - usage_per_day[message_time.date()] += 1 - - start_date = datetime.now().date() - timedelta(days=graph_range) - all_dates = [start_date + timedelta(days=i) for i in range(graph_range)] - - for date in all_dates: - usage_per_day[date] += 0 - - usages = sorted( - [ - Usage(date=date, usage_count=count) - for date, count in usage_per_day.items() - if start_date <= date <= datetime.now().date() - ], - key=lambda usage: usage.date, - ) - - return BrainsUsages(usages=usages) diff --git a/backend/api/quivr_api/modules/analytics/repository/analytics_interface.py b/backend/api/quivr_api/modules/analytics/repository/analytics_interface.py deleted file mode 100644 index 4a0faa1c2..000000000 --- a/backend/api/quivr_api/modules/analytics/repository/analytics_interface.py +++ /dev/null @@ -1,22 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Optional -from uuid import UUID - -from quivr_api.modules.analytics.entity.analytics import BrainsUsages, Range - - -class AnalyticsInterface(ABC): - @abstractmethod - def get_brains_usages( - self, - user_id: UUID, - graph_range: Range = Range.WEEK, - brain_id: Optional[UUID] = None, - ) -> BrainsUsages: - """ - Get user brains usage - Args: - user_id (UUID): The id of the user - brain_id (Optional[UUID]): The id of the brain, optional - """ - pass diff --git a/backend/api/quivr_api/modules/analytics/service/analytics_service.py b/backend/api/quivr_api/modules/analytics/service/analytics_service.py deleted file mode 100644 index 34a90f325..000000000 --- a/backend/api/quivr_api/modules/analytics/service/analytics_service.py +++ /dev/null @@ -1,14 +0,0 @@ -from quivr_api.modules.analytics.repository.analytics import Analytics -from quivr_api.modules.analytics.repository.analytics_interface import ( - AnalyticsInterface, -) - - -class AnalyticsService: - repository: AnalyticsInterface - - def __init__(self): - self.repository = Analytics() - - def get_brains_usages(self, user_id, graph_range, brain_id=None): - return self.repository.get_brains_usages(user_id, graph_range, brain_id) diff --git a/backend/api/quivr_api/modules/api_key/controller/__init__.py b/backend/api/quivr_api/modules/api_key/controller/__init__.py deleted file mode 100644 index cfaef3c48..000000000 --- a/backend/api/quivr_api/modules/api_key/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .api_key_routes import api_key_router diff --git a/backend/api/quivr_api/modules/api_key/controller/api_key_routes.py b/backend/api/quivr_api/modules/api_key/controller/api_key_routes.py deleted file mode 100644 index 7215ae76d..000000000 --- a/backend/api/quivr_api/modules/api_key/controller/api_key_routes.py +++ /dev/null @@ -1,92 +0,0 @@ -from secrets import token_hex -from typing import List -from uuid import uuid4 - -from fastapi import APIRouter, Depends - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.api_key.dto.outputs import ApiKeyInfo -from quivr_api.modules.api_key.entity.api_key import ApiKey -from quivr_api.modules.api_key.repository.api_keys import ApiKeys -from quivr_api.modules.user.entity.user_identity import UserIdentity - -logger = get_logger(__name__) - - -api_key_router = APIRouter() - -api_keys_repository = ApiKeys() - - -@api_key_router.post( - "/api-key", - response_model=ApiKey, - dependencies=[Depends(AuthBearer())], - tags=["API Key"], -) -async def create_api_key(current_user: UserIdentity = Depends(get_current_user)): - """ - Create new API key for the current user. - - - `current_user`: The current authenticated user. - - Returns the newly created API key. - - This endpoint generates a new API key for the current user. The API key is stored in the database and associated with - the user. It returns the newly created API key. - """ - - new_key_id = uuid4() - new_api_key = token_hex(16) - - try: - # Attempt to insert new API key into database - response = api_keys_repository.create_api_key( - new_key_id, new_api_key, current_user.id, "api_key", 30, False - ) - except Exception as e: - logger.error(f"Error creating new API key: {e}") - return {"api_key": "Error creating new API key."} - logger.info(f"Created new API key for user {current_user.email}.") - - return response # type: ignore - - -@api_key_router.delete( - "/api-key/{key_id}", dependencies=[Depends(AuthBearer())], tags=["API Key"] -) -async def delete_api_key( - key_id: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Delete (deactivate) an API key for the current user. - - - `key_id`: The ID of the API key to delete. - - This endpoint deactivates and deletes the specified API key associated with the current user. The API key is marked - as inactive in the database. - - """ - api_keys_repository.delete_api_key(key_id, current_user.id) - - return {"message": "API key deleted."} - - -@api_key_router.get( - "/api-keys", - response_model=List[ApiKeyInfo], - dependencies=[Depends(AuthBearer())], - tags=["API Key"], -) -async def get_api_keys(current_user: UserIdentity = Depends(get_current_user)): - """ - Get all active API keys for the current user. - - - `current_user`: The current authenticated user. - - Returns a list of active API keys with their IDs and creation times. - - This endpoint retrieves all the active API keys associated with the current user. It returns a list of API key objects - containing the key ID and creation time for each API key. - """ - response = api_keys_repository.get_user_api_keys(current_user.id) - return response.data diff --git a/backend/api/quivr_api/modules/api_key/dto/__init__.py b/backend/api/quivr_api/modules/api_key/dto/__init__.py deleted file mode 100644 index 6921f4284..000000000 --- a/backend/api/quivr_api/modules/api_key/dto/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .outputs import ApiKeyInfo diff --git a/backend/api/quivr_api/modules/api_key/dto/outputs.py b/backend/api/quivr_api/modules/api_key/dto/outputs.py deleted file mode 100644 index c8fd924ad..000000000 --- a/backend/api/quivr_api/modules/api_key/dto/outputs.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel - - -class ApiKeyInfo(BaseModel): - key_id: str - creation_time: str diff --git a/backend/api/quivr_api/modules/api_key/entity/api_key.py b/backend/api/quivr_api/modules/api_key/entity/api_key.py deleted file mode 100644 index cdde0ef90..000000000 --- a/backend/api/quivr_api/modules/api_key/entity/api_key.py +++ /dev/null @@ -1,11 +0,0 @@ -from pydantic import BaseModel - - -class ApiKey(BaseModel): - api_key: str - key_id: str - days: int - only_chat: bool - name: str - creation_time: str - is_active: bool diff --git a/backend/api/quivr_api/modules/api_key/repository/__init__.py b/backend/api/quivr_api/modules/api_key/repository/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/api_key/repository/api_key_interface.py b/backend/api/quivr_api/modules/api_key/repository/api_key_interface.py deleted file mode 100644 index efe9a07fd..000000000 --- a/backend/api/quivr_api/modules/api_key/repository/api_key_interface.py +++ /dev/null @@ -1,34 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List -from uuid import UUID - -from quivr_api.modules.api_key.entity.api_key import ApiKey - - -class ApiKeysInterface(ABC): - @abstractmethod - def create_api_key( - self, - new_key_id: UUID, - new_api_key: str, - user_id: UUID, - days: int, - only_chat: bool, - ): - pass - - @abstractmethod - def delete_api_key(self, key_id: UUID, user_id: UUID): - pass - - @abstractmethod - def get_active_api_key(self, api_key: UUID): - pass - - @abstractmethod - def get_user_id_by_api_key(self, api_key: UUID): - pass - - @abstractmethod - def get_user_api_keys(self, user_id: UUID) -> List[ApiKey]: - pass diff --git a/backend/api/quivr_api/modules/api_key/repository/api_keys.py b/backend/api/quivr_api/modules/api_key/repository/api_keys.py deleted file mode 100644 index 7d13e1fad..000000000 --- a/backend/api/quivr_api/modules/api_key/repository/api_keys.py +++ /dev/null @@ -1,82 +0,0 @@ -from datetime import datetime -from typing import Optional -from uuid import UUID - -from quivr_api.modules.api_key.entity.api_key import ApiKey -from quivr_api.modules.api_key.repository.api_key_interface import ApiKeysInterface -from quivr_api.modules.dependencies import get_supabase_client - - -class ApiKeys(ApiKeysInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client # type: ignore - - def create_api_key( - self, new_key_id, new_api_key, user_id, name, days=30, only_chat=False - ) -> Optional[ApiKey]: - response = ( - self.db.table("api_keys") - .insert( - [ - { - "key_id": str(new_key_id), - "user_id": str(user_id), - "api_key": str(new_api_key), - "name": str(name), - "days": int(days), - "only_chat": bool(only_chat), - "creation_time": datetime.utcnow().strftime( - "%Y-%m-%d %H:%M:%S" - ), - "is_active": True, - } - ] - ) - .execute() - ) - if len(response.data) == 0: - return None - return ApiKey(**response.data[0]) - - def delete_api_key(self, key_id: str, user_id: UUID): - return ( - self.db.table("api_keys") - .update( - { - "is_active": False, - "deleted_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), - } - ) - .match({"key_id": key_id, "user_id": user_id}) - .execute() - ) - - def get_active_api_key(self, api_key: str): - response = ( - self.db.table("api_keys") - .select("api_key", "creation_time") - .filter("api_key", "eq", api_key) - .filter("is_active", "eq", str(True)) - .execute() - ) - return response - - def get_user_id_by_api_key(self, api_key: str): - response = ( - self.db.table("api_keys") - .select("user_id") - .filter("api_key", "eq", api_key) - .execute() - ) - return response - - def get_user_api_keys(self, user_id): - response = ( - self.db.table("api_keys") - .select("key_id, creation_time") - .filter("user_id", "eq", user_id) - .filter("is_active", "eq", True) - .execute() - ) - return response.data diff --git a/backend/api/quivr_api/modules/api_key/service/__init__.py b/backend/api/quivr_api/modules/api_key/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/api_key/service/api_key_service.py b/backend/api/quivr_api/modules/api_key/service/api_key_service.py deleted file mode 100644 index 9290a51e4..000000000 --- a/backend/api/quivr_api/modules/api_key/service/api_key_service.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import datetime - -from fastapi import HTTPException - -from quivr_api.logger import get_logger -from quivr_api.modules.api_key.repository.api_key_interface import ApiKeysInterface -from quivr_api.modules.api_key.repository.api_keys import ApiKeys -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_service import UserService - -logger = get_logger(__name__) - - -user_service = UserService() - - -class ApiKeyService: - repository: ApiKeysInterface - - def __init__(self): - self.repository = ApiKeys() - - async def verify_api_key( - self, - api_key: str, - ) -> bool: - try: - # Use UTC time to avoid timezone issues - current_date = datetime.utcnow().date() - result = self.repository.get_active_api_key(api_key) - - if result.data is not None and len(result.data) > 0: - api_key_creation_date = datetime.strptime( - result.data[0]["creation_time"], "%Y-%m-%dT%H:%M:%S" - ).date() - - if api_key_creation_date.year == current_date.year: - return True - return False - except Exception as e: - logger.error(f"Error verifying API key: {e}") - return False - - async def get_user_from_api_key( - self, - api_key: str, - ) -> UserIdentity: - user_id_data = self.repository.get_user_id_by_api_key(api_key) - - if not user_id_data.data: - raise HTTPException(status_code=400, detail="Invalid API key.") - - user_id = user_id_data.data[0]["user_id"] - - # TODO: directly UserService instead - email = user_service.get_user_email_by_user_id(user_id) - - if email is None: - raise HTTPException(status_code=400, detail="Invalid API key.") - - return UserIdentity(email=email, id=user_id) diff --git a/backend/api/quivr_api/modules/api_key/tests/test_api_key.py b/backend/api/quivr_api/modules/api_key/tests/test_api_key.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/__init__.py b/backend/api/quivr_api/modules/assistant/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/controller/__init__.py b/backend/api/quivr_api/modules/assistant/controller/__init__.py deleted file mode 100644 index cc8eb3907..000000000 --- a/backend/api/quivr_api/modules/assistant/controller/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# noqa: -from .assistant_routes import assistant_router - -__all__ = [ - "assistant_router", -] diff --git a/backend/api/quivr_api/modules/assistant/controller/assistant_routes.py b/backend/api/quivr_api/modules/assistant/controller/assistant_routes.py deleted file mode 100644 index e88605139..000000000 --- a/backend/api/quivr_api/modules/assistant/controller/assistant_routes.py +++ /dev/null @@ -1,202 +0,0 @@ -import io -from typing import Annotated, List -from uuid import uuid4 -from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile -import re - -from quivr_api.celery_config import celery -from quivr_api.modules.assistant.dto.inputs import FileInput -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth.auth_bearer import AuthBearer, get_current_user -from quivr_api.modules.assistant.controller.assistants_definition import ( - assistants, - validate_assistant_input, -) -from quivr_api.modules.assistant.dto.inputs import CreateTask, InputAssistant -from quivr_api.modules.assistant.dto.outputs import AssistantOutput -from quivr_api.modules.assistant.entity.assistant_entity import ( - AssistantSettings, -) -from quivr_api.modules.assistant.entity.task_entity import TaskMetadata -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.upload.service.upload_file import ( - upload_file_storage, -) -from quivr_api.modules.user.entity.user_identity import UserIdentity - -logger = get_logger(__name__) - - -assistant_router = APIRouter() - - -TasksServiceDep = Annotated[TasksService, Depends(get_service(TasksService))] -UserIdentityDep = Annotated[UserIdentity, Depends(get_current_user)] - - -@assistant_router.get( - "/assistants", dependencies=[Depends(AuthBearer())], tags=["Assistant"] -) -async def get_assistants( - request: Request, - current_user: UserIdentity = Depends(get_current_user), -) -> List[AssistantOutput]: - logger.info("Getting assistants") - - return assistants - - -@assistant_router.get( - "/assistants/tasks", dependencies=[Depends(AuthBearer())], tags=["Assistant"] -) -async def get_tasks( - request: Request, - current_user: UserIdentityDep, - tasks_service: TasksServiceDep, -): - logger.info("Getting tasks") - return await tasks_service.get_tasks_by_user_id(current_user.id) - - -@assistant_router.post( - "/assistants/task", dependencies=[Depends(AuthBearer())], tags=["Assistant"] -) -async def create_task( - current_user: UserIdentityDep, - tasks_service: TasksServiceDep, - request: Request, - input: str = File(...), - files: List[UploadFile] = None, -): - inputs = InputAssistant.model_validate_json(input) - - assistant = next( - (assistant for assistant in assistants if assistant.id == inputs.id), None - ) - - if assistant is None: - raise HTTPException(status_code=404, detail="Assistant not found") - - is_valid, validation_errors = validate_assistant_input(inputs, assistant) - if not is_valid: - for error in validation_errors: - print(error) - raise HTTPException(status_code=400, detail=error) - else: - print("Assistant input is valid.") - notification_uuid = f"{assistant.name}-{str(uuid4())[:8]}" - - # Process files dynamically - for upload_file in files: - # Sanitize the filename to remove spaces and special characters - sanitized_filename = re.sub(r'[^\w\-_\.]', '_', upload_file.filename) - upload_file.filename = sanitized_filename - - file_name_path = f"{inputs.id}/{notification_uuid}/{sanitized_filename}" - buff_reader = io.BufferedReader(upload_file.file) # type: ignore - try: - await upload_file_storage(buff_reader, file_name_path) - except Exception as e: - logger.exception(f"Exception in upload_route {e}") - raise HTTPException( - status_code=500, detail=f"Failed to upload file to storage. {e}" - ) - logger.info(f"Files are: {files}") - - # Sanitize the filename in input - if inputs.inputs.files: - inputs.inputs.files = [ - FileInput( - value=re.sub(r'[^\w\-_\.]', '_', file.value), - key=file.key - ) - for file in inputs.inputs.files - ] - - task = CreateTask( - assistant_id=inputs.id, - assistant_name=assistant.name, - pretty_id=notification_uuid, - settings=inputs.model_dump(mode="json"), - task_metadata=TaskMetadata( - input_files=[file.filename for file in files] - ).model_dump(mode="json") - if files - else None, # type: ignore - ) - - task_created = await tasks_service.create_task(task, current_user.id) - - celery.send_task( - "process_assistant_task", - kwargs={ - "assistant_id": inputs.id, - "notification_uuid": notification_uuid, - "task_id": task_created.id, - "user_id": str(current_user.id), - }, - ) - return task_created - - -@assistant_router.get( - "/assistants/task/{task_id}", - dependencies=[Depends(AuthBearer())], - tags=["Assistant"], -) -async def get_task( - request: Request, - task_id: str, - current_user: UserIdentityDep, - tasks_service: TasksServiceDep, -): - return await tasks_service.get_task_by_id(task_id, current_user.id) # type: ignore - - -@assistant_router.delete( - "/assistants/task/{task_id}", - dependencies=[Depends(AuthBearer())], - tags=["Assistant"], -) -async def delete_task( - request: Request, - task_id: int, - current_user: UserIdentityDep, - tasks_service: TasksServiceDep, -): - return await tasks_service.delete_task(task_id, current_user.id) - - -@assistant_router.get( - "/assistants/task/{task_id}/download", - dependencies=[Depends(AuthBearer())], - tags=["Assistant"], -) -async def get_download_link_task( - request: Request, - task_id: int, - current_user: UserIdentityDep, - tasks_service: TasksServiceDep, -): - return await tasks_service.get_download_link_task(task_id, current_user.id) - - -@assistant_router.get( - "/assistants/{assistant_id}/config", - dependencies=[Depends(AuthBearer())], - tags=["Assistant"], - response_model=AssistantSettings, - summary="Retrieve assistant configuration", - description="Get the settings and file requirements for the specified assistant.", -) -async def get_assistant_config( - assistant_id: int, - current_user: UserIdentityDep, -): - assistant = next( - (assistant for assistant in assistants if assistant.id == assistant_id), None - ) - if assistant is None: - raise HTTPException(status_code=404, detail="Assistant not found") - return assistant.settings diff --git a/backend/api/quivr_api/modules/assistant/controller/assistants_definition.py b/backend/api/quivr_api/modules/assistant/controller/assistants_definition.py deleted file mode 100644 index c99b197d0..000000000 --- a/backend/api/quivr_api/modules/assistant/controller/assistants_definition.py +++ /dev/null @@ -1,251 +0,0 @@ -from quivr_api.modules.assistant.dto.inputs import InputAssistant -from quivr_api.modules.assistant.dto.outputs import ( - AssistantOutput, - ConditionalInput, - InputBoolean, - InputFile, - Inputs, - InputSelectText, - Pricing, -) - - -def validate_assistant_input( - assistant_input: InputAssistant, assistant_output: AssistantOutput -): - errors = [] - - # Validate files - if assistant_output.inputs.files: - required_files = [ - file for file in assistant_output.inputs.files if file.required - ] - input_files = { - file_input.key for file_input in (assistant_input.inputs.files or []) - } - for req_file in required_files: - if req_file.key not in input_files: - errors.append(f"Missing required file input: {req_file.key}") - - # Validate URLs - if assistant_output.inputs.urls: - required_urls = [url for url in assistant_output.inputs.urls if url.required] - input_urls = { - url_input.key for url_input in (assistant_input.inputs.urls or []) - } - for req_url in required_urls: - if req_url.key not in input_urls: - errors.append(f"Missing required URL input: {req_url.key}") - - # Validate texts - if assistant_output.inputs.texts: - required_texts = [ - text for text in assistant_output.inputs.texts if text.required - ] - input_texts = { - text_input.key for text_input in (assistant_input.inputs.texts or []) - } - for req_text in required_texts: - if req_text.key not in input_texts: - errors.append(f"Missing required text input: {req_text.key}") - else: - # Validate regex if applicable - req_text_val = next( - (t for t in assistant_output.inputs.texts if t.key == req_text.key), - None, - ) - if req_text_val and req_text_val.validation_regex: - import re - - input_value = next( - ( - t.value - for t in assistant_input.inputs.texts - if t.key == req_text.key - ), - "", - ) - if not re.match(req_text_val.validation_regex, input_value): - errors.append( - f"Text input '{req_text.key}' does not match the required format." - ) - - # Validate booleans - if assistant_output.inputs.booleans: - required_booleans = [b for b in assistant_output.inputs.booleans if b.required] - input_booleans = { - b_input.key for b_input in (assistant_input.inputs.booleans or []) - } - for req_bool in required_booleans: - if req_bool.key not in input_booleans: - errors.append(f"Missing required boolean input: {req_bool.key}") - - # Validate numbers - if assistant_output.inputs.numbers: - required_numbers = [n for n in assistant_output.inputs.numbers if n.required] - input_numbers = { - n_input.key for n_input in (assistant_input.inputs.numbers or []) - } - for req_number in required_numbers: - if req_number.key not in input_numbers: - errors.append(f"Missing required number input: {req_number.key}") - else: - # Validate min and max - input_value = next( - ( - n.value - for n in assistant_input.inputs.numbers - if n.key == req_number.key - ), - None, - ) - if req_number.min is not None and input_value < req_number.min: - errors.append( - f"Number input '{req_number.key}' is below minimum value." - ) - if req_number.max is not None and input_value > req_number.max: - errors.append( - f"Number input '{req_number.key}' exceeds maximum value." - ) - - # Validate select_texts - if assistant_output.inputs.select_texts: - required_select_texts = [ - st for st in assistant_output.inputs.select_texts if st.required - ] - input_select_texts = { - st_input.key for st_input in (assistant_input.inputs.select_texts or []) - } - for req_select in required_select_texts: - if req_select.key not in input_select_texts: - errors.append(f"Missing required select text input: {req_select.key}") - else: - input_value = next( - ( - st.value - for st in assistant_input.inputs.select_texts - if st.key == req_select.key - ), - None, - ) - if input_value not in req_select.options: - errors.append(f"Invalid option for select text '{req_select.key}'.") - - # Validate select_numbers - if assistant_output.inputs.select_numbers: - required_select_numbers = [ - sn for sn in assistant_output.inputs.select_numbers if sn.required - ] - input_select_numbers = { - sn_input.key for sn_input in (assistant_input.inputs.select_numbers or []) - } - for req_select in required_select_numbers: - if req_select.key not in input_select_numbers: - errors.append(f"Missing required select number input: {req_select.key}") - else: - input_value = next( - ( - sn.value - for sn in assistant_input.inputs.select_numbers - if sn.key == req_select.key - ), - None, - ) - if input_value not in req_select.options: - errors.append( - f"Invalid option for select number '{req_select.key}'." - ) - - # Validate brain input - if assistant_output.inputs.brain and assistant_output.inputs.brain.required: - if not assistant_input.inputs.brain or not assistant_input.inputs.brain.value: - errors.append("Missing required brain input.") - - if errors: - return False, errors - else: - return True, None - - -assistant1 = AssistantOutput( - id=1, - name="Compliance Check", - description="Allows analyzing the compliance of the information contained in documents against charter or regulatory requirements.", - pricing=Pricing(), - tags=["Disabled"], - input_description="Input description", - output_description="Output description", - inputs=Inputs( - files=[ - InputFile(key="file_1", description="File description"), - InputFile(key="file_2", description="File description"), - ], - ), - icon_url="https://example.com/icon.png", -) - -assistant2 = AssistantOutput( - id=2, - name="Consistency Check", - description="Ensures that the information in one document is replicated identically in another document.", - pricing=Pricing(), - tags=[], - input_description="Input description", - output_description="Output description", - icon_url="https://example.com/icon.png", - inputs=Inputs( - files=[ - InputFile(key="Document 1", description="File description"), - InputFile(key="Document 2", description="File description"), - ], - select_texts=[ - InputSelectText( - key="DocumentsType", - description="Select Documents Type", - options=[ - "Cahier des charges VS Etiquettes", - "Fiche Dev VS Cahier des charges", - ], - ), - ], - ), -) - -assistant3 = AssistantOutput( - id=3, - name="Difference Detection", - description="Highlights differences between one document and another after modifications.", - pricing=Pricing(), - tags=[], - input_description="Input description", - output_description="Output description", - icon_url="https://example.com/icon.png", - inputs=Inputs( - files=[ - InputFile(key="Document 1", description="File description"), - InputFile(key="Document 2", description="File description"), - ], - booleans=[ - InputBoolean( - key="Hard-to-Read Document?", description="Boolean description" - ), - ], - select_texts=[ - InputSelectText( - key="DocumentsType", - description="Select Documents Type", - options=["Etiquettes", "Cahier des charges"], - ), - ], - conditional_inputs=[ - ConditionalInput( - key="DocumentsType", - conditional_key="Hard-to-Read Document?", - condition="equals", - value="Etiquettes", - ), - ], - ), -) - -assistants = [assistant1, assistant2, assistant3] diff --git a/backend/api/quivr_api/modules/assistant/dto/__init__.py b/backend/api/quivr_api/modules/assistant/dto/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/dto/inputs.py b/backend/api/quivr_api/modules/assistant/dto/inputs.py deleted file mode 100644 index 0847224dd..000000000 --- a/backend/api/quivr_api/modules/assistant/dto/inputs.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Dict, List, Optional -from uuid import UUID - -from pydantic import BaseModel, root_validator - - -class CreateTask(BaseModel): - pretty_id: str - assistant_id: int - assistant_name: str - settings: dict - task_metadata: Dict | None = None - - -class BrainInput(BaseModel): - value: Optional[UUID] = None - - @root_validator(pre=True) - def empty_string_to_none(cls, values): - for field, value in values.items(): - if value == "": - values[field] = None - return values - - -class FileInput(BaseModel): - key: str - value: str - - -class UrlInput(BaseModel): - key: str - value: str - - -class TextInput(BaseModel): - key: str - value: str - - -class InputBoolean(BaseModel): - key: str - value: bool - - -class InputNumber(BaseModel): - key: str - value: int - - -class InputSelectText(BaseModel): - key: str - value: str - - -class InputSelectNumber(BaseModel): - key: str - value: int - - -class Inputs(BaseModel): - files: Optional[List[FileInput]] = None - urls: Optional[List[UrlInput]] = None - texts: Optional[List[TextInput]] = None - booleans: Optional[List[InputBoolean]] = None - numbers: Optional[List[InputNumber]] = None - select_texts: Optional[List[InputSelectText]] = None - select_numbers: Optional[List[InputSelectNumber]] = None - brain: Optional[BrainInput] = None - - -class InputAssistant(BaseModel): - id: int - name: str - inputs: Inputs diff --git a/backend/api/quivr_api/modules/assistant/dto/outputs.py b/backend/api/quivr_api/modules/assistant/dto/outputs.py deleted file mode 100644 index 4703be843..000000000 --- a/backend/api/quivr_api/modules/assistant/dto/outputs.py +++ /dev/null @@ -1,105 +0,0 @@ -from typing import List, Optional - -from pydantic import BaseModel - - -class BrainInput(BaseModel): - required: Optional[bool] = True - description: str - type: str - - -class InputFile(BaseModel): - key: str - allowed_extensions: Optional[List[str]] = ["pdf"] - required: Optional[bool] = True - description: str - - -class InputUrl(BaseModel): - key: str - required: Optional[bool] = True - description: str - - -class InputText(BaseModel): - key: str - required: Optional[bool] = True - description: str - validation_regex: Optional[str] = None - - -class InputBoolean(BaseModel): - key: str - required: Optional[bool] = True - description: str - - -class InputNumber(BaseModel): - key: str - required: Optional[bool] = True - description: str - min: Optional[int] = None - max: Optional[int] = None - increment: Optional[int] = None - default: Optional[int] = None - - -class InputSelectText(BaseModel): - key: str - required: Optional[bool] = True - description: str - options: List[str] - default: Optional[str] = None - - -class InputSelectNumber(BaseModel): - key: str - required: Optional[bool] = True - description: str - options: List[int] - default: Optional[int] = None - - -class ConditionalInput(BaseModel): - """ - Conditional input is a list of inputs that are conditional to the value of another input. - key: The key of the input that is conditional. - conditional_key: The key that determines if the input is shown. - """ - - key: str - conditional_key: str - condition: Optional[str] = ( - None # e.g. "equals", "contains", "starts_with", "ends_with", "regex", "in", "not_in", "is_empty", "is_not_empty" - ) - value: Optional[str] = None - - -class Inputs(BaseModel): - files: Optional[List[InputFile]] = None - urls: Optional[List[InputUrl]] = None - texts: Optional[List[InputText]] = None - booleans: Optional[List[InputBoolean]] = None - numbers: Optional[List[InputNumber]] = None - select_texts: Optional[List[InputSelectText]] = None - select_numbers: Optional[List[InputSelectNumber]] = None - brain: Optional[BrainInput] = None - conditional_inputs: Optional[List[ConditionalInput]] = None - - -class Pricing(BaseModel): - cost: int = 20 - description: str = "Credits per use" - - -class AssistantOutput(BaseModel): - id: int - name: str - description: str - pricing: Optional[Pricing] = Pricing() - tags: Optional[List[str]] = [] - input_description: str - output_description: str - inputs: Inputs - icon_url: Optional[str] = None diff --git a/backend/api/quivr_api/modules/assistant/entity/__init__.py b/backend/api/quivr_api/modules/assistant/entity/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/entity/assistant_entity.py b/backend/api/quivr_api/modules/assistant/entity/assistant_entity.py deleted file mode 100644 index 6321ff1f4..000000000 --- a/backend/api/quivr_api/modules/assistant/entity/assistant_entity.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Any, List, Optional - -from pydantic import BaseModel - - -class AssistantFileRequirement(BaseModel): - name: str - description: Optional[str] = None - required: bool = True - accepted_types: Optional[List[str]] = None # e.g., ['text/csv', 'application/json'] - - -class AssistantInput(BaseModel): - name: str - description: str - type: str # e.g., 'boolean', 'uuid', 'string' - required: bool = True - regex: Optional[str] = None - options: Optional[List[Any]] = None # For predefined choices - default: Optional[Any] = None - - -class AssistantSettings(BaseModel): - inputs: List[AssistantInput] - files: Optional[List[AssistantFileRequirement]] = None - - -class Assistant(BaseModel): - id: int - name: str - description: str - settings: AssistantSettings - required_files: Optional[List[str]] = None # List of required file names diff --git a/backend/api/quivr_api/modules/assistant/entity/task_entity.py b/backend/api/quivr_api/modules/assistant/entity/task_entity.py deleted file mode 100644 index 7972c0700..000000000 --- a/backend/api/quivr_api/modules/assistant/entity/task_entity.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import datetime -from typing import Dict, List, Optional -from uuid import UUID - -from pydantic import BaseModel -from sqlmodel import JSON, TIMESTAMP, BigInteger, Column, Field, SQLModel, text - - -class TaskMetadata(BaseModel): - input_files: Optional[List[str]] = None - - -class Task(SQLModel, table=True): - __tablename__ = "tasks" # type: ignore - - id: int | None = Field( - default=None, - sa_column=Column( - BigInteger, - primary_key=True, - autoincrement=True, - ), - ) - assistant_id: int - assistant_name: str - pretty_id: str - user_id: UUID - status: str = Field(default="pending") - creation_time: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - settings: Dict = Field(default_factory=dict, sa_column=Column(JSON)) - answer: str | None = Field(default=None) - task_metadata: Dict | None = Field(default_factory=dict, sa_column=Column(JSON)) diff --git a/backend/api/quivr_api/modules/assistant/repository/__init__.py b/backend/api/quivr_api/modules/assistant/repository/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/repository/interfaces/__init__.py b/backend/api/quivr_api/modules/assistant/repository/interfaces/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/repository/interfaces/task_interface.py b/backend/api/quivr_api/modules/assistant/repository/interfaces/task_interface.py deleted file mode 100644 index 74f2046c6..000000000 --- a/backend/api/quivr_api/modules/assistant/repository/interfaces/task_interface.py +++ /dev/null @@ -1,32 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List -from uuid import UUID - -from quivr_api.modules.assistant.dto.inputs import CreateTask -from quivr_api.modules.assistant.entity.task_entity import Task - - -class TasksInterface(ABC): - @abstractmethod - def create_task(self, task: CreateTask) -> Task: - pass - - @abstractmethod - def get_task_by_id(self, task_id: UUID, user_id: UUID) -> Task: - pass - - @abstractmethod - def delete_task(self, task_id: UUID, user_id: UUID) -> None: - pass - - @abstractmethod - def get_tasks_by_user_id(self, user_id: UUID) -> List[Task]: - pass - - @abstractmethod - def update_task(self, task_id: int, task: dict) -> None: - pass - - @abstractmethod - def get_download_link_task(self, task_id: int, user_id: UUID) -> str: - pass diff --git a/backend/api/quivr_api/modules/assistant/repository/tasks.py b/backend/api/quivr_api/modules/assistant/repository/tasks.py deleted file mode 100644 index cc1aae78d..000000000 --- a/backend/api/quivr_api/modules/assistant/repository/tasks.py +++ /dev/null @@ -1,86 +0,0 @@ -from typing import Sequence -from uuid import UUID - -from sqlalchemy import exc -from sqlalchemy.ext.asyncio import AsyncSession -from sqlmodel import col, select - -from quivr_api.modules.assistant.dto.inputs import CreateTask -from quivr_api.modules.assistant.entity.task_entity import Task -from quivr_api.modules.dependencies import BaseRepository -from quivr_api.modules.upload.service.generate_file_signed_url import ( - generate_file_signed_url, -) - - -class TasksRepository(BaseRepository): - def __init__(self, session: AsyncSession): - super().__init__(session) - - async def create_task(self, task: CreateTask, user_id: UUID) -> Task: - try: - task_to_create = Task( - assistant_id=task.assistant_id, - assistant_name=task.assistant_name, - pretty_id=task.pretty_id, - user_id=user_id, - settings=task.settings, - task_metadata=task.task_metadata, # type: ignore - ) - self.session.add(task_to_create) - await self.session.commit() - except exc.IntegrityError: - await self.session.rollback() - raise Exception() - - await self.session.refresh(task_to_create) - return task_to_create - - async def get_task_by_id(self, task_id: UUID, user_id: UUID) -> Task: - query = select(Task).where(Task.id == task_id, Task.user_id == user_id) - response = await self.session.exec(query) - return response.one() - - async def get_tasks_by_user_id(self, user_id: UUID) -> Sequence[Task]: - query = ( - select(Task).where(Task.user_id == user_id).order_by(col(Task.id).desc()) - ) - response = await self.session.exec(query) - return response.all() - - async def delete_task(self, task_id: int, user_id: UUID) -> None: - query = select(Task).where(Task.id == task_id, Task.user_id == user_id) - response = await self.session.exec(query) - task = response.one() - if task: - await self.session.delete(task) - await self.session.commit() - else: - raise Exception() - - async def update_task(self, task_id: int, task_updates: dict) -> None: - query = select(Task).where(Task.id == task_id) - response = await self.session.exec(query) - task = response.one() - if task: - for key, value in task_updates.items(): - setattr(task, key, value) - await self.session.commit() - else: - raise Exception("Task not found") - - async def get_download_link_task(self, task_id: int, user_id: UUID) -> str: - query = select(Task).where(Task.id == task_id, Task.user_id == user_id) - response = await self.session.exec(query) - task = response.one() - - path = f"{task.assistant_id}/{task.pretty_id}/output.pdf" - - try: - signed_url = generate_file_signed_url(path) - if signed_url and "signedURL" in signed_url: - return signed_url["signedURL"] - else: - raise Exception() - except Exception: - return "error" diff --git a/backend/api/quivr_api/modules/assistant/services/__init__.py b/backend/api/quivr_api/modules/assistant/services/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/assistant/services/tasks_service.py b/backend/api/quivr_api/modules/assistant/services/tasks_service.py deleted file mode 100644 index e7df1f3a6..000000000 --- a/backend/api/quivr_api/modules/assistant/services/tasks_service.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Sequence -from uuid import UUID - -from quivr_api.modules.assistant.dto.inputs import CreateTask -from quivr_api.modules.assistant.entity.task_entity import Task -from quivr_api.modules.assistant.repository.tasks import TasksRepository -from quivr_api.modules.dependencies import BaseService - - -class TasksService(BaseService[TasksRepository]): - repository_cls = TasksRepository - - def __init__(self, repository: TasksRepository): - self.repository = repository - - async def create_task(self, task: CreateTask, user_id: UUID) -> Task: - return await self.repository.create_task(task, user_id) - - async def get_task_by_id(self, task_id: UUID, user_id: UUID) -> Task: - return await self.repository.get_task_by_id(task_id, user_id) - - async def get_tasks_by_user_id(self, user_id: UUID) -> Sequence[Task]: - return await self.repository.get_tasks_by_user_id(user_id) - - async def delete_task(self, task_id: int, user_id: UUID) -> None: - return await self.repository.delete_task(task_id, user_id) - - async def update_task(self, task_id: int, task: dict) -> None: - return await self.repository.update_task(task_id, task) - - async def get_download_link_task(self, task_id: int, user_id: UUID) -> str: - return await self.repository.get_download_link_task(task_id, user_id) diff --git a/backend/api/quivr_api/modules/authorization/utils/__init__.py b/backend/api/quivr_api/modules/authorization/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/base_repository.py b/backend/api/quivr_api/modules/base_repository.py deleted file mode 100644 index f40362bff..000000000 --- a/backend/api/quivr_api/modules/base_repository.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import Any, Generic, Sequence, TypeVar -from uuid import UUID - -from fastapi import HTTPException -from pydantic import BaseModel -from sqlalchemy import exc -from sqlmodel import SQLModel, col, select -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.base_uuid_entity import BaseUUIDModel - -ModelType = TypeVar("ModelType", bound=BaseUUIDModel) -CreateSchema = TypeVar("CreateSchema", bound=BaseModel) -UpdateSchema = TypeVar("UpdateSchema", bound=BaseModel) -T = TypeVar("T", bound=SQLModel) - - -class BaseCRUDRepository(Generic[ModelType, CreateSchema, UpdateSchema]): - def __init__(self, model: type[ModelType], session: AsyncSession): - """ - Base repository for default CRUD operations - """ - self.model = model - self.session = session - - def get_db(self) -> AsyncSession: - return self.session - - async def get_by_id( - self, *, id: UUID, db_session: AsyncSession - ) -> ModelType | None: - query = select(self.model).where(self.model.id == id) - response = await db_session.exec(query) - return response.one() - - async def get_by_ids( - self, - *, - list_ids: list[UUID], - db_session: AsyncSession | None = None, - ) -> Sequence[ModelType] | None: - db_session = db_session or self.session - response = await db_session.exec( - select(self.model).where(col(self.model.id).in_(list_ids)) - ) - return response.all() - - async def get_multi( - self, - *, - skip: int = 0, - limit: int = 100, - db_session: AsyncSession | None = None, - ) -> Sequence[ModelType]: - db_session = db_session or self.session - query = select(self.model).offset(skip).limit(limit) - response = await db_session.exec(query) - return response.all() - - async def create( - self, - *, - entity: CreateSchema | ModelType, - db_session: AsyncSession | None = None, - ) -> ModelType: - db_session = db_session or self.session - db_obj = self.model.model_validate(entity) # type: ignore - - try: - db_session.add(db_obj) - await db_session.commit() - except exc.IntegrityError: - await db_session.rollback() - # TODO(@aminediro) : for now, build an exception system - raise HTTPException( - status_code=409, - detail="Resource already exists", - ) - await db_session.refresh(db_obj) - return db_obj - - async def update( - self, - *, - obj_current: ModelType, - obj_new: UpdateSchema | dict[str, Any] | ModelType, - db_session: AsyncSession | None = None, - ) -> ModelType: - db_session = db_session or self.session - - if isinstance(obj_new, dict): - update_data = obj_new - else: - update_data = obj_new.dict( - exclude_unset=True - ) # This tells Pydantic to not include the values that were not sent - for field in update_data: - setattr(obj_current, field, update_data[field]) - - db_session.add(obj_current) - await db_session.commit() - await db_session.refresh(obj_current) - return obj_current - - async def remove( - self, *, id: UUID | str, db_session: AsyncSession | None = None - ) -> ModelType: - db_session = db_session or self.session - response = await db_session.exec(select(self.model).where(self.model.id == id)) - obj = response.one() - await db_session.delete(obj) - await db_session.commit() - return obj diff --git a/backend/api/quivr_api/modules/base_uuid_entity.py b/backend/api/quivr_api/modules/base_uuid_entity.py deleted file mode 100644 index e402ff6dc..000000000 --- a/backend/api/quivr_api/modules/base_uuid_entity.py +++ /dev/null @@ -1,11 +0,0 @@ -from uuid import UUID - -from sqlmodel import Field, SQLModel - - -class BaseUUIDModel(SQLModel, table=True): - id: UUID = Field( - primary_key=True, - index=True, - nullable=False, - ) diff --git a/backend/api/quivr_api/modules/brain/__init__.py b/backend/api/quivr_api/modules/brain/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/controller/__init__.py b/backend/api/quivr_api/modules/brain/controller/__init__.py deleted file mode 100644 index 98f5cd9dc..000000000 --- a/backend/api/quivr_api/modules/brain/controller/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .brain_routes import brain_router - -__all__ = [ - "brain_router", -] diff --git a/backend/api/quivr_api/modules/brain/controller/brain_routes.py b/backend/api/quivr_api/modules/brain/controller/brain_routes.py deleted file mode 100644 index 2176f5511..000000000 --- a/backend/api/quivr_api/modules/brain/controller/brain_routes.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import Annotated -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException, Request - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth.auth_bearer import AuthBearer, get_current_user -from quivr_api.modules.brain.dto.inputs import ( - BrainQuestionRequest, - BrainUpdatableProperties, - CreateBrainProperties, -) -from quivr_api.modules.brain.entity.brain_entity import ( - BrainType, - MinimalUserBrainEntity, - RoleEnum, -) -from quivr_api.modules.brain.entity.integration_brain import ( - IntegrationDescriptionEntity, -) -from quivr_api.modules.brain.service.brain_authorization_service import ( - has_brain_authorization, -) -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.brain.service.get_question_context_from_brain import ( - get_question_context_from_brain, -) -from quivr_api.modules.brain.service.integration_brain_service import ( - IntegrationBrainDescriptionService, -) -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.prompt.service.prompt_service import PromptService -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_usage import UserUsage -from quivr_api.utils.telemetry import maybe_send_telemetry -from quivr_api.utils.uuid_generator import generate_uuid_from_string - -logger = get_logger(__name__) -brain_router = APIRouter() - -prompt_service = PromptService() -brain_service = BrainService() -brain_user_service = BrainUserService() -integration_brain_description_service = IntegrationBrainDescriptionService() -ModelServiceDep = Annotated[ModelService, Depends(get_service(ModelService))] - - -@brain_router.get( - "/brains/integrations/", - dependencies=[Depends(AuthBearer())], -) -async def get_integration_brain_description() -> list[IntegrationDescriptionEntity]: - """Retrieve the integration brain description.""" - # TODO: Deprecated, remove this endpoint - return [] - - -@brain_router.get("/brains/", dependencies=[Depends(AuthBearer())], tags=["Brain"]) -async def retrieve_all_brains_for_user( - model_service: ModelServiceDep, - current_user: UserIdentity = Depends(get_current_user), -): - """Retrieve all brains for the current user.""" - brains = brain_user_service.get_user_brains(current_user.id) - models = await model_service.get_models() - default_model = await model_service.get_default_model() - - for brain in brains: - # find the brain.model in models and set the brain.price to the model.price - found = False - if brain.model: - for model in models: - if model.name == brain.model: - brain.price = model.price - found = True - break - if not found: - brain.price = default_model.price - - for model in models: - brains.append( - MinimalUserBrainEntity( - id=generate_uuid_from_string(model.name), - status="private", - brain_type=BrainType.model, - name=model.name, - rights=RoleEnum.Viewer, - model=True, - price=model.price, - max_input=model.max_input, - max_output=model.max_output, - display_name=model.display_name, - image_url=model.image_url, - description=model.description, - integration_logo_url="model.integration_id", - max_files=0, - ) - ) - - return {"brains": brains} - - -@brain_router.get( - "/brains/{brain_id}/", - dependencies=[ - Depends(AuthBearer()), - Depends( - has_brain_authorization( - required_roles=[RoleEnum.Owner, RoleEnum.Editor, RoleEnum.Viewer] - ) - ), - ], - tags=["Brain"], -) -async def retrieve_brain_by_id( - brain_id: UUID, - current_user: UserIdentity = Depends(get_current_user), -): - """Retrieve details of a specific brain by its ID.""" - brain_details = brain_service.get_brain_details(brain_id, current_user.id) - if brain_details is None: - raise HTTPException(status_code=404, detail="Brain details not found") - return brain_details - - -@brain_router.post("/brains/", dependencies=[Depends(AuthBearer())], tags=["Brain"]) -async def create_new_brain( - brain: CreateBrainProperties, - request: Request, - current_user: UserIdentity = Depends(get_current_user), -): - """Create a new brain for the user.""" - user_brains = brain_user_service.get_user_brains(current_user.id) - user_usage = UserUsage( - id=current_user.id, - email=current_user.email, - ) - user_settings = user_usage.get_user_settings() - - if len(user_brains) >= user_settings.get("max_brains", 5): - raise HTTPException( - status_code=429, - detail=f"Maximum number of brains reached ({user_settings.get('max_brains', 5)}).", - ) - maybe_send_telemetry("create_brain", {"brain_name": brain.name}, request) - new_brain = brain_service.create_brain( - brain=brain, - user_id=current_user.id, - ) - brain_user_service.create_brain_user( - user_id=current_user.id, - brain_id=new_brain.brain_id, - rights=RoleEnum.Owner, - is_default_brain=True, - ) - - return {"id": new_brain.brain_id, "name": brain.name, "rights": "Owner"} - - -@brain_router.put( - "/brains/{brain_id}/", - dependencies=[ - Depends(AuthBearer()), - Depends(has_brain_authorization([RoleEnum.Editor, RoleEnum.Owner])), - ], - tags=["Brain"], -) -async def update_existing_brain( - brain_id: UUID, - brain_update_data: BrainUpdatableProperties, - current_user: UserIdentity = Depends(get_current_user), -): - """Update an existing brain's configuration.""" - existing_brain = brain_service.get_brain_details(brain_id, current_user.id) - if existing_brain is None: - raise HTTPException(status_code=404, detail="Brain not found") - - if brain_update_data.prompt_id is None and existing_brain.prompt_id: - prompt = prompt_service.get_prompt_by_id(existing_brain.prompt_id) - if prompt and prompt.status == "private": - prompt_service.delete_prompt_by_id(existing_brain.prompt_id) - - return {"message": f"Prompt {brain_id} has been updated."} - - elif brain_update_data.status == "private" and existing_brain.status == "public": - brain_user_service.delete_brain_users(brain_id) - return {"message": f"Brain {brain_id} has been deleted."} - - else: - brain_service.update_brain_by_id(brain_id, brain_update_data) - - return {"message": f"Brain {brain_id} has been updated."} - - -@brain_router.post( - "/brains/{brain_id}/documents", - dependencies=[Depends(AuthBearer()), Depends(has_brain_authorization())], - tags=["Brain"], -) -async def get_question_context_for_brain( - brain_id: UUID, question: BrainQuestionRequest -): - # TODO: Move this endpoint to AnswerGenerator service - """Retrieve the question context from a specific brain.""" - context = get_question_context_from_brain(brain_id, question.question) - return {"docs": context} diff --git a/backend/api/quivr_api/modules/brain/dto/__init__.py b/backend/api/quivr_api/modules/brain/dto/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/dto/inputs.py b/backend/api/quivr_api/modules/brain/dto/inputs.py deleted file mode 100644 index bbdbb1801..000000000 --- a/backend/api/quivr_api/modules/brain/dto/inputs.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import BrainType -from quivr_api.modules.brain.entity.integration_brain import IntegrationType - -logger = get_logger(__name__) - - -class CreateIntegrationBrain(BaseModel, extra="ignore"): - integration_name: str - integration_logo_url: str - connection_settings: dict - integration_type: IntegrationType - description: str - max_files: int - - -class BrainIntegrationSettings(BaseModel, extra="ignore"): - integration_id: str - settings: dict - - -class BrainIntegrationUpdateSettings(BaseModel, extra="ignore"): - settings: dict - - -class CreateBrainProperties(BaseModel, extra="ignore"): - name: Optional[str] = "Default brain" - description: str = "This is a description" - status: Optional[str] = "private" - model: Optional[str] = None - temperature: Optional[float] = 0.0 - max_tokens: Optional[int] = 2000 - prompt_id: Optional[UUID] = None - brain_type: Optional[BrainType] = BrainType.doc - integration: Optional[BrainIntegrationSettings] = None - snippet_color: Optional[str] = None - snippet_emoji: Optional[str] = None - - def dict(self, *args, **kwargs): - brain_dict = super().dict(*args, **kwargs) - if brain_dict.get("prompt_id"): - brain_dict["prompt_id"] = str(brain_dict.get("prompt_id")) - return brain_dict - - -class BrainUpdatableProperties(BaseModel, extra="ignore"): - name: Optional[str] = None - description: Optional[str] = None - temperature: Optional[float] = None - model: Optional[str] = None - max_tokens: Optional[int] = None - status: Optional[str] = None - prompt_id: Optional[UUID] = None - integration: Optional[BrainIntegrationUpdateSettings] = None - snippet_color: Optional[str] = None - snippet_emoji: Optional[str] = None - - def dict(self, *args, **kwargs): - brain_dict = super().dict(*args, **kwargs) - if brain_dict.get("prompt_id"): - brain_dict["prompt_id"] = str(brain_dict.get("prompt_id")) - return brain_dict - - -class BrainQuestionRequest(BaseModel): - question: str diff --git a/backend/api/quivr_api/modules/brain/entity/__init__.py b/backend/api/quivr_api/modules/brain/entity/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/entity/brain_entity.py b/backend/api/quivr_api/modules/brain/entity/brain_entity.py deleted file mode 100644 index 708b8d482..000000000 --- a/backend/api/quivr_api/modules/brain/entity/brain_entity.py +++ /dev/null @@ -1,133 +0,0 @@ -from datetime import datetime -from enum import Enum -from typing import List, Optional -from uuid import UUID - -from pydantic import BaseModel -from quivr_core.config import BrainConfig -from sqlalchemy.dialects.postgresql import ENUM as PGEnum -from sqlalchemy.ext.asyncio import AsyncAttrs -from sqlmodel import TIMESTAMP, Column, Field, Relationship, SQLModel, text -from sqlmodel import UUID as PGUUID - -from quivr_api.modules.brain.entity.integration_brain import ( - IntegrationDescriptionEntity, - IntegrationEntity, -) -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB -from quivr_api.modules.knowledge.entity.knowledge_brain import KnowledgeBrain - -# from sqlmodel import Enum as PGEnum -from quivr_api.modules.prompt.entity.prompt import Prompt - - -class BrainType(str, Enum): - doc = "doc" - api = "api" - composite = "composite" - integration = "integration" - model = "model" - - -class Brain(AsyncAttrs, SQLModel, table=True): - __tablename__ = "brains" # type: ignore - - brain_id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - name: str - description: str - status: str | None = None - model: str | None = None - max_tokens: int | None = None - temperature: float | None = None - last_update: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - brain_type: BrainType | None = Field( - sa_column=Column( - PGEnum(BrainType, name="brain_type_enum", create_type=False), - default=BrainType.integration, - ), - ) - brain_chat_history: List["ChatHistory"] = Relationship( # type: ignore # noqa: F821 - back_populates="brain", sa_relationship_kwargs={"lazy": "select"} - ) - prompt_id: UUID | None = Field(default=None, foreign_key="prompts.id") - prompt: Prompt | None = Relationship( # noqa: F821 - back_populates="brain", sa_relationship_kwargs={"lazy": "joined"} - ) - knowledges: List[KnowledgeDB] = Relationship( - back_populates="brains", link_model=KnowledgeBrain - ) - - # TODO : add - # "meaning" "public"."vector", - # "tags" "public"."tags"[] - - -class BrainEntity(BrainConfig): - last_update: datetime | None = None - brain_type: BrainType | None = None - description: Optional[str] = None - temperature: Optional[float] = None - meaning: Optional[str] = None - openai_api_key: Optional[str] = None - tags: Optional[List[str]] = None - model: Optional[str] = None - max_tokens: Optional[int] = None - status: Optional[str] = None - prompt_id: Optional[UUID] = None - integration: Optional[IntegrationEntity] = None - integration_description: Optional[IntegrationDescriptionEntity] = None - snippet_emoji: Optional[str] = None - snippet_color: Optional[str] = None - - def dict(self, **kwargs): - data = super().dict( - **kwargs, - ) - data["id"] = self.id - return data - - -class RoleEnum(str, Enum): - Viewer = "Viewer" - Editor = "Editor" - Owner = "Owner" - - -class BrainUser(BaseModel): - id: UUID - user_id: UUID - rights: RoleEnum - default_brain: bool = False - - -class MinimalUserBrainEntity(BaseModel): - id: UUID - name: str - brain_model: Optional[str] = None - rights: RoleEnum - status: str - brain_type: BrainType - description: str - integration_logo_url: str - max_files: int - price: Optional[int] = None - max_input: Optional[int] = None - max_output: Optional[int] = None - display_name: Optional[str] = None - image_url: Optional[str] = None - model: bool = False - snippet_color: Optional[str] = None - snippet_emoji: Optional[str] = None diff --git a/backend/api/quivr_api/modules/brain/entity/integration_brain.py b/backend/api/quivr_api/modules/brain/entity/integration_brain.py deleted file mode 100644 index 61d46fd20..000000000 --- a/backend/api/quivr_api/modules/brain/entity/integration_brain.py +++ /dev/null @@ -1,46 +0,0 @@ -from enum import Enum -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel - - -class IntegrationType(str, Enum): - CUSTOM = "custom" - SYNC = "sync" - DOC = "doc" - - -class IntegrationBrainTag(str, Enum): - NEW = "new" - RECOMMENDED = "recommended" - MOST_POPULAR = "most_popular" - PREMIUM = "premium" - COMING_SOON = "coming_soon" - COMMUNITY = "community" - DEPRECATED = "deprecated" - - -class IntegrationDescriptionEntity(BaseModel): - id: UUID - integration_name: str - integration_logo_url: Optional[str] = None - connection_settings: Optional[dict] = None - integration_type: IntegrationType - tags: Optional[list[IntegrationBrainTag]] = [] - information: Optional[str] = None - description: str - max_files: int - allow_model_change: bool - integration_display_name: str - onboarding_brain: bool - - -class IntegrationEntity(BaseModel): - id: int - user_id: str - brain_id: str - integration_id: str - settings: Optional[dict] = None - credentials: Optional[dict] = None - last_synced: str diff --git a/backend/api/quivr_api/modules/brain/integrations/Big/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Big/Brain.py deleted file mode 100644 index 141f7de7c..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Big/Brain.py +++ /dev/null @@ -1,146 +0,0 @@ -import json -from typing import AsyncIterable -from uuid import UUID - -from langchain.chains import ConversationalRetrievalChain, LLMChain -from langchain.chains.question_answering import load_qa_chain -from langchain_community.chat_models import ChatLiteLLM -from langchain_core.prompts.chat import ( - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -) -from langchain_core.prompts.prompt import PromptTemplate - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.chat.dto.chats import ChatQuestion - -logger = get_logger(__name__) - - -class BigBrain(KnowledgeBrainQA): - """ - The BigBrain class integrates advanced conversational retrieval and language model chains - to provide comprehensive and context-aware responses to user queries. - - It leverages a combination of document retrieval, question condensation, and document-based - question answering to generate responses that are informed by a wide range of knowledge sources. - """ - - def __init__( - self, - **kwargs, - ): - """ - Initializes the BigBrain class with specific configurations. - - Args: - **kwargs: Arbitrary keyword arguments. - """ - super().__init__( - **kwargs, - ) - - def get_chain(self): - """ - Constructs and returns the conversational QA chain used by BigBrain. - - Returns: - A ConversationalRetrievalChain instance. - """ - system_template = """Combine these summaries in a way that makes sense and answer the user's question. - Use markdown or any other techniques to display the content in a nice and aerated way. Answer in the language of the question. - Here are user instructions on how to respond: {custom_personality} - ______________________ - {summaries}""" - messages = [ - SystemMessagePromptTemplate.from_template(system_template), - HumanMessagePromptTemplate.from_template("{question}"), - ] - CHAT_COMBINE_PROMPT = ChatPromptTemplate.from_messages(messages) - - ### Question prompt - question_prompt_template = """Use the following portion of a long document to see if any of the text is relevant to answer the question. - Return any relevant text verbatim. Return the answer in the same language as the question. If the answer is not in the text, just say nothing in the same language as the question. - {context} - Question: {question} - Relevant text, if any, else say Nothing:""" - QUESTION_PROMPT = PromptTemplate( - template=question_prompt_template, input_variables=["context", "question"] - ) - - ### Condense Question Prompt - - _template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question in exactly the same language as the original question. - - Chat History: - {chat_history} - Follow Up Input: {question} - Standalone question in same language as question:""" - CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template) - - api_base = None - if self.brain_settings.ollama_api_base_url and self.model.startswith("ollama"): - api_base = self.brain_settings.ollama_api_base_url - - llm = ChatLiteLLM( - temperature=0, - model=self.model, - api_base=api_base, - max_tokens=self.max_tokens, - ) - - retriever_doc = self.knowledge_qa.get_retriever() - - question_generator = LLMChain(llm=llm, prompt=CONDENSE_QUESTION_PROMPT) - doc_chain = load_qa_chain( - llm, - chain_type="map_reduce", - question_prompt=QUESTION_PROMPT, - combine_prompt=CHAT_COMBINE_PROMPT, - ) - - chain = ConversationalRetrievalChain( - retriever=retriever_doc, - question_generator=question_generator, - combine_docs_chain=doc_chain, - ) - - return chain - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - """ - Generates a stream of responses for a given question in real-time. - - Args: - chat_id (UUID): The unique identifier for the chat session. - question (ChatQuestion): The question object containing the user's query. - save_answer (bool): Flag indicating whether to save the answer to the chat history. - - Returns: - An asynchronous iterable of response strings. - """ - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - response_tokens = [] - - async for chunk in conversational_qa_chain.astream( - { - "question": question.question, - "chat_history": transformed_history, - "custom_personality": ( - self.prompt_to_use.content if self.prompt_to_use else None - ), - } - ): - if "answer" in chunk: - response_tokens.append(chunk["answer"]) - streamed_chat_history.assistant = chunk["answer"] - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) diff --git a/backend/api/quivr_api/modules/brain/integrations/Big/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Big/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/Claude/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Claude/Brain.py deleted file mode 100644 index 25732779e..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Claude/Brain.py +++ /dev/null @@ -1,101 +0,0 @@ -import json -from typing import AsyncIterable -from uuid import UUID - -from langchain_community.chat_models import ChatLiteLLM -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder - -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.chat.dto.chats import ChatQuestion - - -class ClaudeBrain(KnowledgeBrainQA): - """ - ClaudeBrain integrates with Claude model to provide conversational AI capabilities. - It leverages the Claude model for generating responses based on the provided context. - - Attributes: - **kwargs: Arbitrary keyword arguments for KnowledgeBrainQA initialization. - """ - - def __init__( - self, - **kwargs, - ): - """ - Initializes the ClaudeBrain with the given arguments. - - Args: - **kwargs: Arbitrary keyword arguments. - """ - super().__init__( - **kwargs, - ) - - def calculate_pricing(self): - """ - Calculates the pricing for using the ClaudeBrain. - - Returns: - int: The pricing value. - """ - return 3 - - def get_chain(self): - """ - Constructs and returns the conversational chain for ClaudeBrain. - - Returns: - A conversational chain object. - """ - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are Claude powered by Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - - chain = prompt | ChatLiteLLM( - model="claude-3-haiku-20240307", max_tokens=self.max_tokens - ) - - return chain - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - """ - Generates a stream of responses for the given question. - - Args: - chat_id (UUID): The chat session ID. - question (ChatQuestion): The question object. - save_answer (bool): Whether to save the answer. - - Yields: - AsyncIterable: A stream of response strings. - """ - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - response_tokens = [] - - async for chunk in conversational_qa_chain.astream( - { - "question": question.question, - "chat_history": transformed_history, - "custom_personality": ( - self.prompt_to_use.content if self.prompt_to_use else None - ), - } - ): - response_tokens.append(chunk.content) - streamed_chat_history.assistant = chunk.content - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) diff --git a/backend/api/quivr_api/modules/brain/integrations/Claude/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Claude/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/GPT4/Brain.py b/backend/api/quivr_api/modules/brain/integrations/GPT4/Brain.py deleted file mode 100644 index 0083b48cc..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/GPT4/Brain.py +++ /dev/null @@ -1,283 +0,0 @@ -import json -import operator -from typing import Annotated, AsyncIterable, List, Optional, Sequence, TypedDict -from uuid import UUID - -from langchain.tools import BaseTool -from langchain_core.messages import BaseMessage, ToolMessage -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -from langchain_core.tools import BaseTool -from langchain_openai import ChatOpenAI -from langgraph.graph import END, StateGraph -from langgraph.prebuilt import ToolExecutor, ToolInvocation - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.chat.dto.chats import ChatQuestion -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.tools import ( - EmailSenderTool, - ImageGeneratorTool, - URLReaderTool, - WebSearchTool, -) - - -class AgentState(TypedDict): - messages: Annotated[Sequence[BaseMessage], operator.add] - - -logger = get_logger(__name__) - -chat_service = get_service(ChatService)() - - -class GPT4Brain(KnowledgeBrainQA): - """ - GPT4Brain integrates with GPT-4 to provide real-time answers and supports various tools to enhance its capabilities. - - Available Tools: - - WebSearchTool: Performs web searches to find relevant information. - - ImageGeneratorTool: Generates images based on textual descriptions. - - URLReaderTool: Reads and summarizes content from URLs. - - EmailSenderTool: Sends emails with specified content. - - Use Cases: - - WebSearchTool can be used to find the latest news articles on a specific topic or to gather information from various websites. - - ImageGeneratorTool is useful for creating visual content based on textual prompts, such as generating a company logo based on a description. - - URLReaderTool can be used to summarize articles or web pages, making it easier to quickly understand the content without reading the entire text. - - EmailSenderTool enables automated email sending, such as sending a summary of a meeting's minutes to all participants. - """ - - tools: Optional[List[BaseTool]] = None - tool_executor: Optional[ToolExecutor] = None - function_model: ChatOpenAI = None - - def __init__( - self, - **kwargs, - ): - super().__init__( - **kwargs, - ) - self.tools = [ - WebSearchTool(), - ImageGeneratorTool(), - URLReaderTool(), - EmailSenderTool(user_email=self.user_email), - ] - self.tool_executor = ToolExecutor(tools=self.tools) - - def calculate_pricing(self): - return 3 - - def should_continue(self, state): - messages = state["messages"] - last_message = messages[-1] - # Make sure there is a previous message - - if last_message.tool_calls: - name = last_message.tool_calls[0]["name"] - if name == "image-generator": - return "final" - # If there is no function call, then we finish - if not last_message.tool_calls: - return "end" - # Otherwise if there is, we check if it's suppose to return direct - else: - return "continue" - - # Define the function that calls the model - def call_model(self, state): - messages = state["messages"] - response = self.function_model.invoke(messages) - # We return a list, because this will get added to the existing list - return {"messages": [response]} - - # Define the function to execute tools - def call_tool(self, state): - messages = state["messages"] - # Based on the continue condition - # we know the last message involves a function call - last_message = messages[-1] - # We construct an ToolInvocation from the function_call - tool_call = last_message.tool_calls[0] - tool_name = tool_call["name"] - arguments = tool_call["args"] - - action = ToolInvocation( - tool=tool_call["name"], - tool_input=tool_call["args"], - ) - # We call the tool_executor and get back a response - response = self.tool_executor.invoke(action) - # We use the response to create a FunctionMessage - function_message = ToolMessage( - content=str(response), name=action.tool, tool_call_id=tool_call["id"] - ) - # We return a list, because this will get added to the existing list - return {"messages": [function_message]} - - def create_graph(self): - # Define a new graph - workflow = StateGraph(AgentState) - - # Define the two nodes we will cycle between - workflow.add_node("agent", self.call_model) - workflow.add_node("action", self.call_tool) - workflow.add_node("final", self.call_tool) - - # Set the entrypoint as `agent` - # This means that this node is the first one called - workflow.set_entry_point("agent") - - # We now add a conditional edge - workflow.add_conditional_edges( - # First, we define the start node. We use `agent`. - # This means these are the edges taken after the `agent` node is called. - "agent", - # Next, we pass in the function that will determine which node is called next. - self.should_continue, - # Finally we pass in a mapping. - # The keys are strings, and the values are other nodes. - # END is a special node marking that the graph should finish. - # What will happen is we will call `should_continue`, and then the output of that - # will be matched against the keys in this mapping. - # Based on which one it matches, that node will then be called. - { - # If `tools`, then we call the tool node. - "continue": "action", - # Final call - "final": "final", - # Otherwise we finish. - "end": END, - }, - ) - - # We now add a normal edge from `tools` to `agent`. - # This means that after `tools` is called, `agent` node is called next. - workflow.add_edge("action", "agent") - workflow.add_edge("final", END) - - # Finally, we compile it! - # This compiles it into a LangChain Runnable, - # meaning you can use it as you would any other runnable - app = workflow.compile() - return app - - def get_chain(self): - self.function_model = ChatOpenAI(model="gpt-4o", temperature=0, streaming=True) - - self.function_model = self.function_model.bind_tools(self.tools) - - graph = self.create_graph() - - return graph - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - filtered_history = self.filter_history(transformed_history, 40, 2000) - response_tokens = [] - config = {"metadata": {"conversation_id": str(chat_id)}} - - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are GPT-4 powered by Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - prompt_formated = prompt.format_messages( - chat_history=filtered_history, - question=question.question, - custom_personality=( - self.prompt_to_use.content if self.prompt_to_use else None - ), - ) - - async for event in conversational_qa_chain.astream_events( - {"messages": prompt_formated}, - config=config, - version="v1", - ): - kind = event["event"] - if kind == "on_chat_model_stream": - content = event["data"]["chunk"].content - if content: - # Empty content in the context of OpenAI or Anthropic usually means - # that the model is asking for a tool to be invoked. - # So we only print non-empty content - response_tokens.append(content) - streamed_chat_history.assistant = content - yield f"data: {json.dumps(streamed_chat_history.dict())}" - elif kind == "on_tool_start": - print("--") - print( - f"Starting tool: {event['name']} with inputs: {event['data'].get('input')}" - ) - elif kind == "on_tool_end": - print(f"Done tool: {event['name']}") - print(f"Tool output was: {event['data'].get('output')}") - print("--") - elif kind == "on_chain_end": - output = event["data"]["output"] - final_output = [item for item in output if "final" in item] - if final_output: - if ( - final_output[0]["final"]["messages"][0].name - == "image-generator" - ): - final_message = final_output[0]["final"]["messages"][0].content - response_tokens.append(final_message) - streamed_chat_history.assistant = final_message - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) - - def generate_answer( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> GetChatHistoryOutput: - conversational_qa_chain = self.get_chain() - transformed_history, _ = self.initialize_streamed_chat_history( - chat_id, question - ) - filtered_history = self.filter_history(transformed_history, 40, 2000) - config = {"metadata": {"conversation_id": str(chat_id)}} - - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are GPT-4 powered by Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - prompt_formated = prompt.format_messages( - chat_history=filtered_history, - question=question.question, - custom_personality=( - self.prompt_to_use.content if self.prompt_to_use else None - ), - ) - model_response = conversational_qa_chain.invoke( - {"messages": prompt_formated}, - config=config, - ) - - answer = model_response["messages"][-1].content - - return self.save_non_streaming_answer( - chat_id=chat_id, question=question, answer=answer, metadata={} - ) diff --git a/backend/api/quivr_api/modules/brain/integrations/GPT4/__init__.py b/backend/api/quivr_api/modules/brain/integrations/GPT4/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/Multi_Contract/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Multi_Contract/Brain.py deleted file mode 100644 index 9cef1960c..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Multi_Contract/Brain.py +++ /dev/null @@ -1,195 +0,0 @@ -import datetime -from operator import itemgetter -from typing import List - -from langchain.prompts import HumanMessagePromptTemplate, SystemMessagePromptTemplate -from langchain_community.chat_models import ChatLiteLLM -from langchain_core.output_parsers import StrOutputParser -from langchain_core.prompts import ChatPromptTemplate, PromptTemplate -from langchain_core.pydantic_v1 import BaseModel as BaseModelV1 -from langchain_core.pydantic_v1 import Field as FieldV1 -from langchain_core.runnables import RunnableLambda, RunnablePassthrough -from langchain_openai import ChatOpenAI - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA - -logger = get_logger(__name__) - - -class cited_answer(BaseModelV1): - """Answer the user question based only on the given sources, and cite the sources used.""" - - answer: str = FieldV1( - ..., - description="The answer to the user question, which is based only on the given sources.", - ) - citations: List[int] = FieldV1( - ..., - description="The integer IDs of the SPECIFIC sources which justify the answer.", - ) - - followup_questions: List[str] = FieldV1( - ..., - description="Generate up to 3 follow-up questions that could be asked based on the answer given or context provided.", - ) - - -# First step is to create the Rephrasing Prompt -_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language. Keep as much details as possible from previous messages. Keep entity names and all. - -Chat History: -{chat_history} -Follow Up Input: {question} -Standalone question:""" -CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template) - -# Next is the answering prompt - -template_answer = """ -Context: -{context} - -User Question: {question} -Answer: -""" - -today_date = datetime.datetime.now().strftime("%B %d, %Y") - -system_message_template = ( - f"Your name is Quivr. You're a helpful assistant. Today's date is {today_date}." -) - -system_message_template += """ -When answering use markdown neat. -Answer in a concise and clear manner. -Use the following pieces of context from files provided by the user to answer the users. -Answer in the same language as the user question. -If you don't know the answer with the context provided from the files, just say that you don't know, don't try to make up an answer. -Don't cite the source id in the answer objects, but you can use the source to answer the question. -You have access to the files to answer the user question (limited to first 20 files): -{files} - -If not None, User instruction to follow to answer: {custom_instructions} -Don't cite the source id in the answer objects, but you can use the source to answer the question. -""" - - -ANSWER_PROMPT = ChatPromptTemplate.from_messages( - [ - SystemMessagePromptTemplate.from_template(system_message_template), - HumanMessagePromptTemplate.from_template(template_answer), - ] -) - - -# How we format documents - -DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template( - template="Source: {index} \n {page_content}" -) - - -class MultiContractBrain(KnowledgeBrainQA): - """ - The MultiContract class integrates advanced conversational retrieval and language model chains - to provide comprehensive and context-aware responses to user queries. - - It leverages a combination of document retrieval, question condensation, and document-based - question answering to generate responses that are informed by a wide range of knowledge sources. - """ - - def __init__( - self, - **kwargs, - ): - """ - Initializes the MultiContract class with specific configurations. - - Args: - **kwargs: Arbitrary keyword arguments. - """ - super().__init__( - **kwargs, - ) - - def get_chain(self): - list_files_array = ( - await self.knowledge_qa.knowledge_service.get_all_knowledge_in_brain( - self.brain_id - ) - ) # pyright: ignore reportPrivateUsage=none - - list_files_array = [file.file_name for file in list_files_array] - # Max first 10 files - if len(list_files_array) > 20: - list_files_array = list_files_array[:20] - - list_files = "\n".join(list_files_array) if list_files_array else "None" - - retriever_doc = self.knowledge_qa.get_retriever() - - loaded_memory = RunnablePassthrough.assign( - chat_history=RunnableLambda( - lambda x: self.filter_history(x["chat_history"]), - ), - question=lambda x: x["question"], - ) - - api_base = None - if self.brain_settings.ollama_api_base_url and self.model.startswith("ollama"): - api_base = self.brain_settings.ollama_api_base_url - - standalone_question = { - "standalone_question": { - "question": lambda x: x["question"], - "chat_history": itemgetter("chat_history"), - } - | CONDENSE_QUESTION_PROMPT - | ChatLiteLLM(temperature=0, model=self.model, api_base=api_base) - | StrOutputParser(), - } - - knowledge_qa = self.knowledge_qa - prompt_custom_user = knowledge_qa.prompt_to_use() - prompt_to_use = "None" - if prompt_custom_user: - prompt_to_use = prompt_custom_user.content - - # Now we retrieve the documents - retrieved_documents = { - "docs": itemgetter("standalone_question") | retriever_doc, - "question": lambda x: x["standalone_question"], - "custom_instructions": lambda x: prompt_to_use, - } - - final_inputs = { - "context": lambda x: self.knowledge_qa._combine_documents(x["docs"]), - "question": itemgetter("question"), - "custom_instructions": itemgetter("custom_instructions"), - "files": lambda x: list_files, - } - llm = ChatLiteLLM( - max_tokens=self.max_tokens, - model=self.model, - temperature=self.temperature, - api_base=api_base, - ) # pyright: ignore reportPrivateUsage=none - if self.model_compatible_with_function_calling(self.model): - # And finally, we do the part that returns the answers - llm_function = ChatOpenAI( - max_tokens=self.max_tokens, - model=self.model, - temperature=self.temperature, - ) - llm = llm_function.bind_tools( - [cited_answer], - tool_choice="cited_answer", - ) - - answer = { - "answer": final_inputs | ANSWER_PROMPT | llm, - "docs": itemgetter("docs"), - } - - return loaded_memory | standalone_question | retrieved_documents | answer diff --git a/backend/api/quivr_api/modules/brain/integrations/Multi_Contract/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Multi_Contract/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/Notion/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Notion/Brain.py deleted file mode 100644 index 13df2c5d7..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Notion/Brain.py +++ /dev/null @@ -1,25 +0,0 @@ -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA - - -class NotionBrain(KnowledgeBrainQA): - """ - NotionBrain integrates with Notion to provide knowledge-based responses. - It leverages data stored in Notion to answer user queries. - - Attributes: - **kwargs: Arbitrary keyword arguments for KnowledgeBrainQA initialization. - """ - - def __init__( - self, - **kwargs, - ): - """ - Initializes the NotionBrain with the given arguments. - - Args: - **kwargs: Arbitrary keyword arguments. - """ - super().__init__( - **kwargs, - ) diff --git a/backend/api/quivr_api/modules/brain/integrations/Notion/Notion_connector.py b/backend/api/quivr_api/modules/brain/integrations/Notion/Notion_connector.py deleted file mode 100644 index c07024257..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Notion/Notion_connector.py +++ /dev/null @@ -1,395 +0,0 @@ -import os -import tempfile -import time -from io import BytesIO -from typing import Any, List, Optional - -import requests -from fastapi import UploadFile -from pydantic import BaseModel - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.integration_brain import IntegrationEntity -from quivr_api.modules.brain.repository.integration_brains import ( - Integration, - IntegrationBrain, -) -from quivr_api.modules.knowledge.dto.inputs import CreateKnowledgeProperties -from quivr_api.modules.knowledge.repository.knowledge_interface import ( - KnowledgeInterface, -) -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.upload.service.upload_file import upload_file_storage - -logger = get_logger(__name__) - - -class NotionPage(BaseModel): - """Represents a Notion Page object to be used in the NotionConnector class""" - - id: str - created_time: str - last_edited_time: str - archived: bool - properties: dict[str, Any] - url: str - - -class NotionSearchResponse(BaseModel): - """Represents the response from the Notion Search API""" - - results: list[dict[str, Any]] - next_cursor: Optional[str] = None - has_more: bool = False - - -class NotionConnector(IntegrationBrain, Integration): - """A class to interact with the Notion API""" - - credentials: dict[str, str] = None - integration_details: IntegrationEntity = None - brain_id: str = None - user_id: str = None - knowledge_service: KnowledgeInterface - recursive_index_enabled: bool = False - max_pages: int = 100 - - def __init__(self, brain_id: str, user_id: str): - super().__init__() - self.brain_id = brain_id - self.user_id = user_id - self._load_credentials() - self.knowledge_service = KnowledgeService() - - def _load_credentials(self) -> dict[str, str]: - """Load the Notion credentials""" - self.integration_details = self.get_integration_brain(self.brain_id) - if self.credentials is None: - logger.info("Loading Notion credentials") - self.integration_details.credentials = { - "notion_integration_token": self.integration_details.settings.get( - "notion_integration_token", "" - ) - } - self.update_integration_brain( - self.brain_id, self.user_id, self.integration_details - ) - self.credentials = self.integration_details.credentials - else: # pragma: no cover - self.credentials = self.integration_details.credentials - - def _headers(self) -> dict[str, str]: - """Get the headers for the Notion API""" - return { - "Authorization": f'Bearer {self.credentials["notion_integration_token"]}', - "Content-Type": "application/json", - "Notion-Version": "2022-06-28", - } - - def _search_notion(self, query_dict: dict[str, Any]) -> NotionSearchResponse: - """ - Search for pages from a Notion database. - """ - # Use self.credentials to authenticate the request - headers = self._headers() - res = requests.post( - "https://api.notion.com/v1/search", - headers=headers, - json=query_dict, - # Adjust the timeout as needed - timeout=10, - ) - res.raise_for_status() - return NotionSearchResponse(**res.json()) - - def _fetch_blocks(self, page_id: str, cursor: str | None = None) -> dict[str, Any]: - """ - Fetch the blocks of a Notion page. - """ - logger.info(f"Fetching blocks for page: {page_id}") - headers = self._headers() - query_params = None if not cursor else {"start_cursor": cursor} - res = requests.get( - f"https://api.notion.com/v1/blocks/{page_id}/children", - params=query_params, - headers=headers, - timeout=10, - ) - res.raise_for_status() - return res.json() - - def _fetch_page(self, page_id: str) -> dict[str, Any]: - """ - Fetch a Notion page. - """ - logger.info(f"Fetching page: {page_id}") - headers = self._headers() - block_url = f"https://api.notion.com/v1/pages/{page_id}" - res = requests.get( - block_url, - headers=headers, - timeout=10, - ) - try: - res.raise_for_status() - except Exception: - logger.exception(f"Error fetching page - {res.json()}") - return None - return NotionPage(**res.json()) - - def _read_blocks( - self, page_block_id: str - ) -> tuple[list[tuple[str, str]], list[str]]: - """Reads blocks for a page""" - result_lines: list[tuple[str, str]] = [] - child_pages: list[str] = [] - cursor = None - while True: - data = self._fetch_blocks(page_block_id, cursor) - - for result in data["results"]: - result_block_id = result["id"] - result_type = result["type"] - result_obj = result[result_type] - - cur_result_text_arr = [] - if "rich_text" in result_obj: - for rich_text in result_obj["rich_text"]: - # skip if doesn't have text object - if "text" in rich_text: - text = rich_text["text"]["content"] - cur_result_text_arr.append(text) - - if result["has_children"]: - if result_type == "child_page": - child_pages.append(result_block_id) - else: - logger.info(f"Entering sub-block: {result_block_id}") - subblock_result_lines, subblock_child_pages = self._read_blocks( - result_block_id - ) - logger.info(f"Finished sub-block: {result_block_id}") - result_lines.extend(subblock_result_lines) - child_pages.extend(subblock_child_pages) - - # if result_type == "child_database" and self.recursive_index_enabled: - # child_pages.extend(self._read_pages_from_database(result_block_id)) - - cur_result_text = "\n".join(cur_result_text_arr) - if cur_result_text: - result_lines.append((cur_result_text, result_block_id)) - - if data["next_cursor"] is None: - break - - cursor = data["next_cursor"] - - return result_lines, child_pages - - def _read_page_title(self, page: NotionPage) -> str: - """Extracts the title from a Notion page""" - page_title = None - for _, prop in page.properties.items(): - if prop["type"] == "title" and len(prop["title"]) > 0: - page_title = " ".join([t["plain_text"] for t in prop["title"]]).strip() - break - if page_title is None: - page_title = f"Untitled Page [{page.id}]" - page_title = "".join(e for e in page_title if e.isalnum()) - return page_title - - def _read_page_url(self, page: NotionPage) -> str: - """Extracts the URL from a Notion page""" - return page.url - - def _read_pages_from_database(self, database_id: str) -> list[str]: - """Reads pages from a Notion database""" - headers = self._headers() - res = requests.post( - f"https://api.notion.com/v1/databases/{database_id}/query", - headers=headers, - timeout=10, - ) - res.raise_for_status() - return [page["id"] for page in res.json()["results"]] - - def _read_page(self, page_id: str) -> tuple[str, list[str]]: - """Reads a Notion page""" - page = self._fetch_page(page_id) - if page is None: - return None, None, None, None - page_title = self._read_page_title(page) - page_content, child_pages = self._read_blocks(page_id) - page_url = self._read_page_url(page) - return page_title, page_content, child_pages, page_url - - def _filter_pages_by_time( - self, - pages: list[dict[str, Any]], - start: str, - filter_field: str = "last_edited_time", - ) -> list[NotionPage]: - filtered_pages: list[NotionPage] = [] - start_time = time.mktime( - time.strptime(start, "%Y-%m-%dT%H:%M:%S.%f%z") - ) # Convert `start` to a float - for page in pages: - compare_time = time.mktime( - time.strptime(page[filter_field], "%Y-%m-%dT%H:%M:%S.%f%z") - ) - if compare_time > start_time: # Compare `compare_time` with `start_time` - filtered_pages += [NotionPage(**page)] - return filtered_pages - - def get_all_pages(self) -> list[NotionPage]: - """ - Get all the pages from Notion. - """ - query_dict = { - "filter": {"property": "object", "value": "page"}, - "page_size": 100, - } - max_pages = self.max_pages - pages_count = 0 - while True: - search_response = self._search_notion(query_dict) - for page in search_response.results: - pages_count += 1 - if pages_count > max_pages: - break - yield NotionPage(**page) - - if search_response.has_more: - query_dict["start_cursor"] = search_response.next_cursor - else: - break - - def add_file_to_knowledge( - self, page_content: List[tuple[str, str]], page_name: str, page_url: str - ): - """ - Add a file to the knowledge base - """ - logger.info(f"Adding file to knowledge: {page_name}") - filename_with_brain_id = ( - str(self.brain_id) + "/" + str(page_name) + "_notion.txt" - ) - try: - concatened_page_content = "" - if page_content: - for content in page_content: - concatened_page_content += content[0] + "\n" - - # Create a BytesIO object from the content - content_io = BytesIO(concatened_page_content.encode("utf-8")) - - # Create a file of type UploadFile - file = UploadFile(filename=filename_with_brain_id, file=content_io) - - # Write the UploadFile content to a temporary file - with tempfile.NamedTemporaryFile(delete=False) as temp_file: - temp_file.write(file.file.read()) - temp_file_path = temp_file.name - - # Upload the temporary file to the knowledge base - response = upload_file_storage( - temp_file_path, filename_with_brain_id, "true" - ) - logger.info(f"File {response} uploaded successfully") - - # Delete the temporary file - os.remove(temp_file_path) - - knowledge_to_add = CreateKnowledgeProperties( - brain_id=self.brain_id, - file_name=page_name + "_notion.txt", - extension="txt", - integration="notion", - integration_link=page_url, - ) - - added_knowledge = self.knowledge_service.insert_knowledge( - knowledge_to_add - ) - logger.info(f"Knowledge {added_knowledge} added successfully") - - celery.send_task( - "process_file_task", - kwargs={ - "file_name": filename_with_brain_id, - "file_original_name": page_name + "_notion.txt", - "brain_id": self.brain_id, - "delete_file": True, - }, - ) - except Exception: - logger.error("Error adding knowledge") - - def load(self): - """ - Get all the pages, blocks, databases from Notion into a single document per page - """ - all_pages = list(self.get_all_pages()) # Convert generator to list - documents = [] - for page in all_pages: - logger.info(f"Reading page: {page.id}") - page_title, page_content, child_pages, page_url = self._read_page(page.id) - document = { - "page_title": page_title, - "page_content": page_content, - "child_pages": child_pages, - "page_url": page_url, - } - documents.append(document) - self.add_file_to_knowledge(page_content, page_title, page_url) - return documents - - def poll(self): - """ - Update all the brains with the latest data from Notion - """ - integration = self.get_integration_brain(self.brain_id) - last_synced = integration.last_synced - - query_dict = { - "page_size": self.max_pages, - "sort": {"timestamp": "last_edited_time", "direction": "descending"}, - "filter": {"property": "object", "value": "page"}, - } - documents = [] - - while True: - db_res = self._search_notion(query_dict) - pages = self._filter_pages_by_time( - db_res.results, last_synced, filter_field="last_edited_time" - ) - for page in pages: - logger.info(f"Reading page: {page.id}") - page_title, page_content, child_pages, page_url = self._read_page( - page.id - ) - document = { - "page_title": page_title, - "page_content": page_content, - "child_pages": child_pages, - "page_url": page_url, - } - documents.append(document) - self.add_file_to_knowledge(page_content, page_title, page_url) - if not db_res.has_more: - break - query_dict["start_cursor"] = db_res.next_cursor - logger.info( - f"last Synced: {self.update_last_synced(self.brain_id, self.user_id)}" - ) - return documents - - -if __name__ == "__main__": - notion = NotionConnector( - brain_id="73f7d092-d596-4fd0-b24f-24031e9b53cd", - user_id="39418e3b-0258-4452-af60-7acfcc1263ff", - ) - - print(notion.poll()) diff --git a/backend/api/quivr_api/modules/brain/integrations/Notion/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Notion/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/Proxy/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Proxy/Brain.py deleted file mode 100644 index 4d5baa142..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Proxy/Brain.py +++ /dev/null @@ -1,135 +0,0 @@ -import json -from typing import AsyncIterable -from uuid import UUID - -from langchain_community.chat_models import ChatLiteLLM -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.chat.dto.chats import ChatQuestion -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.dependencies import get_service - -logger = get_logger(__name__) - -chat_service = get_service(ChatService)() - - -class ProxyBrain(KnowledgeBrainQA): - """ - ProxyBrain class serves as a proxy to utilize various language models for generating responses. - It dynamically selects and uses the appropriate language model based on the provided context and question. - """ - - def __init__( - self, - **kwargs, - ): - """ - Initializes the ProxyBrain with the given arguments. - - Args: - **kwargs: Arbitrary keyword arguments. - """ - super().__init__( - **kwargs, - ) - - def get_chain(self): - """ - Constructs and returns the conversational chain for ProxyBrain. - - Returns: - A conversational chain object. - """ - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - - chain = prompt | ChatLiteLLM(model=self.model, max_tokens=self.max_tokens) - - return chain - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - """ - Generates a stream of responses for the given question. - - Args: - chat_id (UUID): The chat session ID. - question (ChatQuestion): The question object. - save_answer (bool): Whether to save the answer. - - Yields: - AsyncIterable: A stream of response strings. - """ - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - response_tokens = [] - config = {"metadata": {"conversation_id": str(chat_id)}} - - async for chunk in conversational_qa_chain.astream( - { - "question": question.question, - "chat_history": transformed_history, - "custom_personality": ( - self.prompt_to_use.content if self.prompt_to_use else None - ), - }, - config=config, - ): - response_tokens.append(chunk.content) - streamed_chat_history.assistant = chunk.content - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) - - def generate_answer( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> GetChatHistoryOutput: - """ - Generates a non-streaming answer for the given question. - - Args: - chat_id (UUID): The chat session ID. - question (ChatQuestion): The question object. - save_answer (bool): Whether to save the answer. - - Returns: - GetChatHistoryOutput: The chat history output object containing the answer. - """ - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - config = {"metadata": {"conversation_id": str(chat_id)}} - model_response = conversational_qa_chain.invoke( - { - "question": question.question, - "chat_history": transformed_history, - "custom_personality": ( - self.prompt_to_use.content if self.prompt_to_use else None - ), - }, - config=config, - ) - - answer = model_response.content - - return self.save_non_streaming_answer( - chat_id=chat_id, - question=question, - answer=answer, - ) diff --git a/backend/api/quivr_api/modules/brain/integrations/Proxy/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Proxy/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/SQL/Brain.py b/backend/api/quivr_api/modules/brain/integrations/SQL/Brain.py deleted file mode 100644 index 12a01d4fb..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/SQL/Brain.py +++ /dev/null @@ -1,104 +0,0 @@ -import json -from typing import AsyncIterable -from uuid import UUID - -from langchain_community.chat_models import ChatLiteLLM -from langchain_community.utilities import SQLDatabase -from langchain_core.output_parsers import StrOutputParser -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables import RunnablePassthrough - -from quivr_api.modules.brain.integrations.SQL.SQL_connector import SQLConnector -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.brain.repository.integration_brains import IntegrationBrain -from quivr_api.modules.chat.dto.chats import ChatQuestion - - -class SQLBrain(KnowledgeBrainQA, IntegrationBrain): - """This is the Notion brain class. it is a KnowledgeBrainQA has the data is stored locally. - It is going to call the Data Store internally to get the data. - - Args: - KnowledgeBrainQA (_type_): A brain that store the knowledge internaly - """ - - uri: str = None - db: SQLDatabase = None - sql_connector: SQLConnector = None - - def __init__( - self, - **kwargs, - ): - super().__init__( - **kwargs, - ) - self.sql_connector = SQLConnector(self.brain_id, self.user_id) - - def get_schema(self, _): - return self.db.get_table_info() - - def run_query(self, query): - return self.db.run(query) - - def get_chain(self): - template = """Based on the table schema below, write a SQL query that would answer the user's question: - {schema} - - Question: {question} - SQL Query:""" - prompt = ChatPromptTemplate.from_template(template) - - self.db = SQLDatabase.from_uri(self.sql_connector.credentials["uri"]) - - api_base = None - if self.brain_settings.ollama_api_base_url and self.model.startswith("ollama"): - api_base = self.brain_settings.ollama_api_base_url - - model = ChatLiteLLM(model=self.model, api_base=api_base) - - sql_response = ( - RunnablePassthrough.assign(schema=self.get_schema) - | prompt - | model.bind(stop=["\nSQLResult:"]) - | StrOutputParser() - ) - - template = """Based on the table schema below, question, sql query, and sql response, write a natural language response and the query that was used to generate it.: - {schema} - - Question: {question} - SQL Query: {query} - SQL Response: {response}""" - prompt_response = ChatPromptTemplate.from_template(template) - - full_chain = ( - RunnablePassthrough.assign(query=sql_response).assign( - schema=self.get_schema, - response=lambda x: self.db.run(x["query"]), - ) - | prompt_response - | model - ) - - return full_chain - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - response_tokens = [] - - async for chunk in conversational_qa_chain.astream( - { - "question": question.question, - } - ): - response_tokens.append(chunk.content) - streamed_chat_history.assistant = chunk.content - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) diff --git a/backend/api/quivr_api/modules/brain/integrations/SQL/SQL_connector.py b/backend/api/quivr_api/modules/brain/integrations/SQL/SQL_connector.py deleted file mode 100644 index c5a849214..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/SQL/SQL_connector.py +++ /dev/null @@ -1,41 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.integration_brain import IntegrationEntity -from quivr_api.modules.brain.repository.integration_brains import IntegrationBrain -from quivr_api.modules.knowledge.repository.knowledge_interface import ( - KnowledgeInterface, -) -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService - -logger = get_logger(__name__) - - -class SQLConnector(IntegrationBrain): - """A class to interact with an SQL database""" - - credentials: dict[str, str] = None - integration_details: IntegrationEntity = None - brain_id: str = None - user_id: str = None - knowledge_service: KnowledgeInterface - - def __init__(self, brain_id: str, user_id: str): - super().__init__() - self.brain_id = brain_id - self.user_id = user_id - self._load_credentials() - self.knowledge_service = KnowledgeService() - - def _load_credentials(self) -> dict[str, str]: - """Load the Notion credentials""" - self.integration_details = self.get_integration_brain(self.brain_id) - if self.credentials is None: - logger.info("Loading Notion credentials") - self.integration_details.credentials = { - "uri": self.integration_details.settings.get("uri", "") - } - self.update_integration_brain( - self.brain_id, self.user_id, self.integration_details - ) - self.credentials = self.integration_details.credentials - else: # pragma: no cover - self.credentials = self.integration_details.credentials diff --git a/backend/api/quivr_api/modules/brain/integrations/SQL/__init__.py b/backend/api/quivr_api/modules/brain/integrations/SQL/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/Self/Brain.py b/backend/api/quivr_api/modules/brain/integrations/Self/Brain.py deleted file mode 100644 index 6a992f687..000000000 --- a/backend/api/quivr_api/modules/brain/integrations/Self/Brain.py +++ /dev/null @@ -1,487 +0,0 @@ -import json -from typing import AsyncIterable, List -from uuid import UUID - -from langchain_core.output_parsers import StrOutputParser -from langchain_core.prompts import ( - ChatPromptTemplate, - MessagesPlaceholder, - PromptTemplate, -) -from langchain_core.pydantic_v1 import BaseModel as BaseModelV1 -from langchain_core.pydantic_v1 import Field as FieldV1 -from langchain_openai import ChatOpenAI -from langgraph.graph import END, StateGraph -from typing_extensions import TypedDict - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA -from quivr_api.modules.chat.dto.chats import ChatQuestion -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.dependencies import get_service - - -# Post-processing -def format_docs(docs): - return "\n\n".join(doc.page_content for doc in docs) - - -class GraphState(TypedDict): - """ - Represents the state of our graph. - - Attributes: - question: question - generation: LLM generation - documents: list of documents - """ - - question: str - generation: str - documents: List[str] - - -# Data model -class GradeDocuments(BaseModelV1): - """Binary score for relevance check on retrieved documents.""" - - binary_score: str = FieldV1( - description="Documents are relevant to the question, 'yes' or 'no'" - ) - - -class GradeHallucinations(BaseModelV1): - """Binary score for hallucination present in generation answer.""" - - binary_score: str = FieldV1( - description="Answer is grounded in the facts, 'yes' or 'no'" - ) - - -# Data model -class GradeAnswer(BaseModelV1): - """Binary score to assess answer addresses question.""" - - binary_score: str = FieldV1( - description="Answer addresses the question, 'yes' or 'no'" - ) - - -logger = get_logger(__name__) - -chat_service = get_service(ChatService)() - - -class SelfBrain(KnowledgeBrainQA): - """ - GPT4Brain integrates with GPT-4 to provide real-time answers and supports various tools to enhance its capabilities. - - Available Tools: - - WebSearchTool: Performs web searches to find relevant information. - - ImageGeneratorTool: Generates images based on textual descriptions. - - URLReaderTool: Reads and summarizes content from URLs. - - EmailSenderTool: Sends emails with specified content. - - Use Cases: - - WebSearchTool can be used to find the latest news articles on a specific topic or to gather information from various websites. - - ImageGeneratorTool is useful for creating visual content based on textual prompts, such as generating a company logo based on a description. - - URLReaderTool can be used to summarize articles or web pages, making it easier to quickly understand the content without reading the entire text. - - EmailSenderTool enables automated email sending, such as sending a summary of a meeting's minutes to all participants. - """ - - max_input: int = 10000 - - def __init__( - self, - **kwargs, - ): - super().__init__( - **kwargs, - ) - - def calculate_pricing(self): - return 3 - - def retrieval_grade(self): - llm = ChatOpenAI(model="gpt-4o", temperature=0) - structured_llm_grader = llm.with_structured_output(GradeDocuments) - - # Prompt - system = """You are a grader assessing relevance of a retrieved document to a user question. \n - It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \n - If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n - Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.""" - grade_prompt = ChatPromptTemplate.from_messages( - [ - ("system", system), - ( - "human", - "Retrieved document: \n\n {document} \n\n User question: {question}", - ), - ] - ) - - retrieval_grader = grade_prompt | structured_llm_grader - - return retrieval_grader - - def generation_rag(self): - # Prompt - human_prompt = """You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise. - - Question: {question} - - Context: {context} - - Answer: - """ - prompt_human = PromptTemplate.from_template(human_prompt) - # LLM - llm = ChatOpenAI(model="gpt-4o", temperature=0) - - # Chain - rag_chain = prompt_human | llm | StrOutputParser() - - return rag_chain - - def hallucination_grader(self): - # LLM with function call - llm = ChatOpenAI(model="gpt-4o", temperature=0) - structured_llm_grader = llm.with_structured_output(GradeHallucinations) - - # Prompt - system = """You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. \n - Give a binary score 'yes' or 'no'. 'Yes' means that the answer is grounded in / supported by the set of facts.""" - hallucination_prompt = ChatPromptTemplate.from_messages( - [ - ("system", system), - ( - "human", - "Set of facts: \n\n {documents} \n\n LLM generation: {generation}", - ), - ] - ) - - hallucination_grader = hallucination_prompt | structured_llm_grader - - return hallucination_grader - - def answer_grader(self): - # LLM with function call - llm = ChatOpenAI(model="gpt-4o", temperature=0) - structured_llm_grader = llm.with_structured_output(GradeAnswer) - - # Prompt - system = """You are a grader assessing whether an answer addresses / resolves a question \n - Give a binary score 'yes' or 'no'. Yes' means that the answer resolves the question.""" - answer_prompt = ChatPromptTemplate.from_messages( - [ - ("system", system), - ( - "human", - "User question: \n\n {question} \n\n LLM generation: {generation}", - ), - ] - ) - - answer_grader = answer_prompt | structured_llm_grader - - return answer_grader - - def question_rewriter(self): - # LLM - llm = ChatOpenAI(model="gpt-4o", temperature=0) - - # Prompt - system = """You a question re-writer that converts an input question to a better version that is optimized \n - for vectorstore retrieval. Look at the input and try to reason about the underlying semantic intent / meaning.""" - re_write_prompt = ChatPromptTemplate.from_messages( - [ - ("system", system), - ( - "human", - "Here is the initial question: \n\n {question} \n Formulate an improved question.", - ), - ] - ) - - question_rewriter = re_write_prompt | llm | StrOutputParser() - - return question_rewriter - - def get_chain(self): - graph = self.create_graph() - - return graph - - def create_graph(self): - workflow = StateGraph(GraphState) - - # Define the nodes - workflow.add_node("retrieve", self.retrieve) # retrieve - workflow.add_node("grade_documents", self.grade_documents) # grade documents - workflow.add_node("generate", self.generate) # generatae - workflow.add_node("transform_query", self.transform_query) # transform_query - - # Build graph - workflow.set_entry_point("retrieve") - workflow.add_edge("retrieve", "grade_documents") - workflow.add_conditional_edges( - "grade_documents", - self.decide_to_generate, - { - "transform_query": "transform_query", - "generate": "generate", - }, - ) - workflow.add_edge("transform_query", "retrieve") - workflow.add_conditional_edges( - "generate", - self.grade_generation_v_documents_and_question, - { - "not supported": "generate", - "useful": END, - "not useful": "transform_query", - }, - ) - - # Compile - app = workflow.compile() - return app - - def retrieve(self, state): - """ - Retrieve documents - - Args: - state (dict): The current graph state - - Returns: - state (dict): New key added to state, documents, that contains retrieved documents - """ - print("---RETRIEVE---") - logger.info("Retrieving documents") - question = state["question"] - logger.info(f"Question: {question}") - - # Retrieval - retriever = self.knowledge_qa.get_retriever() - documents = retriever.get_relevant_documents(question) - return {"documents": documents, "question": question} - - def generate(self, state): - """ - Generate answer - - Args: - state (dict): The current graph state - - Returns: - state (dict): New key added to state, generation, that contains LLM generation - """ - print("---GENERATE---") - question = state["question"] - documents = state["documents"] - - formatted_docs = format_docs(documents) - # RAG generation - generation = self.generation_rag().invoke( - {"context": formatted_docs, "question": question} - ) - return {"documents": documents, "question": question, "generation": generation} - - def grade_documents(self, state): - """ - Determines whether the retrieved documents are relevant to the question. - - Args: - state (dict): The current graph state - - Returns: - state (dict): Updates documents key with only filtered relevant documents - """ - - print("---CHECK DOCUMENT RELEVANCE TO QUESTION---") - question = state["question"] - documents = state["documents"] - - # Score each doc - filtered_docs = [] - for d in documents: - score = self.retrieval_grade().invoke( - {"question": question, "document": d.page_content} - ) - grade = score.binary_score - if grade == "yes": - print("---GRADE: DOCUMENT RELEVANT---") - filtered_docs.append(d) - else: - print("---GRADE: DOCUMENT NOT RELEVANT---") - continue - return {"documents": filtered_docs, "question": question} - - def transform_query(self, state): - """ - Transform the query to produce a better question. - - Args: - state (dict): The current graph state - - Returns: - state (dict): Updates question key with a re-phrased question - """ - - print("---TRANSFORM QUERY---") - question = state["question"] - documents = state["documents"] - - # Re-write question - better_question = self.question_rewriter().invoke({"question": question}) - return {"documents": documents, "question": better_question} - - def decide_to_generate(self, state): - """ - Determines whether to generate an answer, or re-generate a question. - - Args: - state (dict): The current graph state - - Returns: - str: Binary decision for next node to call - """ - - print("---ASSESS GRADED DOCUMENTS---") - question = state["question"] - filtered_documents = state["documents"] - - if not filtered_documents: - # All documents have been filtered check_relevance - # We will re-generate a new query - print( - "---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---" - ) - return "transform_query" - else: - # We have relevant documents, so generate answer - print("---DECISION: GENERATE---") - return "generate" - - def grade_generation_v_documents_and_question(self, state): - """ - Determines whether the generation is grounded in the document and answers question. - - Args: - state (dict): The current graph state - - Returns: - str: Decision for next node to call - """ - - print("---CHECK HALLUCINATIONS---") - question = state["question"] - documents = state["documents"] - generation = state["generation"] - - score = self.hallucination_grader().invoke( - {"documents": documents, "generation": generation} - ) - grade = score.binary_score - - # Check hallucination - if grade == "yes": - print("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---") - # Check question-answering - print("---GRADE GENERATION vs QUESTION---") - score = self.answer_grader().invoke( - {"question": question, "generation": generation} - ) - grade = score.binary_score - if grade == "yes": - print("---DECISION: GENERATION ADDRESSES QUESTION---") - return "useful" - else: - print("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---") - return "not useful" - else: - print("---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---") - return "not supported" - - async def generate_stream( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> AsyncIterable: - conversational_qa_chain = self.get_chain() - transformed_history, streamed_chat_history = ( - self.initialize_streamed_chat_history(chat_id, question) - ) - filtered_history = self.filter_history(transformed_history, 40, 2000) - response_tokens = [] - config = {"metadata": {"conversation_id": str(chat_id)}} - - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are GPT-4 powered by Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - prompt_formated = prompt.format_messages( - chat_history=filtered_history, - question=question.question, - custom_personality=( - self.prompt_to_use.content if self.prompt_to_use else None - ), - ) - - async for event in conversational_qa_chain.astream( - {"question": question.question}, config=config - ): - for key, value in event.items(): - if "generation" in value and value["generation"] != "": - response_tokens.append(value["generation"]) - streamed_chat_history.assistant = value["generation"] - - yield f"data: {json.dumps(streamed_chat_history.dict())}" - - self.save_answer(question, response_tokens, streamed_chat_history, save_answer) - - def generate_answer( - self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True - ) -> GetChatHistoryOutput: - conversational_qa_chain = self.get_chain() - transformed_history, _ = self.initialize_streamed_chat_history( - chat_id, question - ) - filtered_history = self.filter_history(transformed_history, 40, 2000) - config = {"metadata": {"conversation_id": str(chat_id)}} - - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are GPT-4 powered by Quivr. You are an assistant. {custom_personality}", - ), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - prompt_formated = prompt.format_messages( - chat_history=filtered_history, - question=question.question, - custom_personality=( - self.prompt_to_use.content if self.prompt_to_use else None - ), - ) - model_response = conversational_qa_chain.invoke( - {"messages": prompt_formated}, - config=config, - ) - - answer = model_response["messages"][-1].content - - return self.save_non_streaming_answer( - chat_id=chat_id, question=question, answer=answer, metadata={} - ) diff --git a/backend/api/quivr_api/modules/brain/integrations/Self/__init__.py b/backend/api/quivr_api/modules/brain/integrations/Self/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/integrations/__init__.py b/backend/api/quivr_api/modules/brain/integrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/repository/__init__.py b/backend/api/quivr_api/modules/brain/repository/__init__.py deleted file mode 100644 index 46e807777..000000000 --- a/backend/api/quivr_api/modules/brain/repository/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .brains import Brains -from .brains_users import BrainsUsers -from .brains_vectors import BrainsVectors -from .integration_brains import IntegrationBrain, IntegrationDescription diff --git a/backend/api/quivr_api/modules/brain/repository/brains.py b/backend/api/quivr_api/modules/brain/repository/brains.py deleted file mode 100644 index 5fc18beef..000000000 --- a/backend/api/quivr_api/modules/brain/repository/brains.py +++ /dev/null @@ -1,103 +0,0 @@ -from uuid import UUID - -from sqlalchemy import text - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.dto.inputs import BrainUpdatableProperties -from quivr_api.modules.brain.entity.brain_entity import BrainEntity -from quivr_api.modules.brain.repository.interfaces.brains_interface import ( - BrainsInterface, -) -from quivr_api.modules.dependencies import ( - get_embedding_client, - get_pg_database_engine, - get_supabase_client, -) - -logger = get_logger(__name__) - - -class Brains(BrainsInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - pg_engine = get_pg_database_engine() - self.pg_engine = pg_engine - - def create_brain(self, brain): - embeddings = get_embedding_client() - string_to_embed = f"Name: {brain.name} Description: {brain.description}" - brain_meaning = embeddings.embed_query(string_to_embed) - brain_dict = brain.dict( - exclude={ - "integration", - } - ) - brain_dict["meaning"] = brain_meaning - response = (self.db.table("brains").insert(brain_dict)).execute() - - return BrainEntity(**response.data[0]) - - def update_brain_last_update_time(self, brain_id): - try: - with self.pg_engine.begin() as connection: - query = """ - UPDATE brains - SET last_update = now() - WHERE brain_id = '{brain_id}' - """ - connection.execute(text(query.format(brain_id=brain_id))) - except Exception as e: - logger.error(e) - - def get_brain_details(self, brain_id): - with self.pg_engine.begin() as connection: - query = """ - SELECT * FROM brains - WHERE brain_id = '{brain_id}' - """ - response = connection.execute( - text(query.format(brain_id=brain_id)) - ).fetchall() - if len(response) == 0: - return None - return BrainEntity(**response[0]._mapping) - - def delete_brain(self, brain_id: str): - with self.pg_engine.begin() as connection: - results = connection.execute( - text(f"DELETE FROM brains WHERE brain_id = '{brain_id}'") - ) - - return results - - def update_brain_by_id( - self, brain_id: UUID, brain: BrainUpdatableProperties - ) -> BrainEntity | None: - embeddings = get_embedding_client() - string_to_embed = f"Name: {brain.name} Description: {brain.description}" - brain_meaning = embeddings.embed_query(string_to_embed) - brain_dict = brain.dict(exclude_unset=True) - brain_dict["meaning"] = brain_meaning - update_brain_response = ( - self.db.table("brains") - .update(brain_dict) - .match({"brain_id": brain_id}) - .execute() - ).data - - if len(update_brain_response) == 0: - return None - - return BrainEntity(**update_brain_response[0]) - - def get_brain_by_id(self, brain_id: UUID) -> BrainEntity | None: - # TODO: merge this method with get_brain_details - with self.pg_engine.begin() as connection: - response = connection.execute( - text(f"SELECT * FROM brains WHERE brain_id = '{brain_id}'") - ).fetchall() - - if len(response) == 0: - return None - return BrainEntity(**response[0]._mapping) diff --git a/backend/api/quivr_api/modules/brain/repository/brains_users.py b/backend/api/quivr_api/modules/brain/repository/brains_users.py deleted file mode 100644 index 9176eeb35..000000000 --- a/backend/api/quivr_api/modules/brain/repository/brains_users.py +++ /dev/null @@ -1,210 +0,0 @@ -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import ( - BrainUser, - MinimalUserBrainEntity, -) -from quivr_api.modules.brain.repository.interfaces.brains_users_interface import ( - BrainsUsersInterface, -) -from quivr_api.modules.dependencies import get_embedding_client, get_supabase_client - -logger = get_logger(__name__) - - -class BrainsUsers(BrainsUsersInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - - def update_meaning(self, brain: MinimalUserBrainEntity): - embeddings = get_embedding_client() - string_to_embed = f"Name: {brain.name} Description: {brain.description}" - brain_meaning = embeddings.embed_query(string_to_embed) - brain_dict = {"meaning": brain_meaning} - response = ( - self.db.table("brains") - .update(brain_dict) - .match({"brain_id": brain.id}) - .execute() - ).data - - if len(response) == 0: - return False - - return True - - def get_user_brains(self, user_id) -> list[MinimalUserBrainEntity]: - response = ( - self.db.from_("brains_users") - .select( - "id:brain_id, rights, brains (brain_id, name, status, brain_type, model, description, meaning, snippet_color, snippet_emoji, integrations_user (brain_id, integration_id, integrations (id, integration_name, integration_logo_url, max_files)))" - ) - .filter("user_id", "eq", user_id) - .execute() - ) - user_brains: list[MinimalUserBrainEntity] = [] - for item in response.data: - integration_logo_url = "" - max_files = 5000 - if item["brains"]["brain_type"] == "integration": - if "integrations_user" in item["brains"]: - for integration_user in item["brains"]["integrations_user"]: - if "integrations" in integration_user: - integration_logo_url = integration_user["integrations"][ - "integration_logo_url" - ] - max_files = integration_user["integrations"]["max_files"] - - user_brains.append( - MinimalUserBrainEntity( - id=item["brains"]["brain_id"], - brain_model=item["brains"]["model"], - name=item["brains"]["name"], - rights=item["rights"], - status=item["brains"]["status"], - brain_type=item["brains"]["brain_type"], - description=( - item["brains"]["description"] - if item["brains"]["description"] is not None - else "" - ), - integration_logo_url=str(integration_logo_url), - max_files=max_files, - snippet_color=item["brains"]["snippet_color"], - snippet_emoji=item["brains"]["snippet_emoji"], - ) - ) - user_brains[-1].rights = item["rights"] - if item["brains"]["meaning"] is None: - self.update_meaning(user_brains[-1]) - - return user_brains - - def get_brain_for_user(self, user_id, brain_id): - response = ( - self.db.from_("brains_users") - .select( - "id:brain_id, rights, brains (id: brain_id, status, name, brain_type, description)" - ) - .filter("user_id", "eq", user_id) - .filter("brain_id", "eq", brain_id) - .execute() - ) - if len(response.data) == 0: - return None - brain_data = response.data[0] - - return MinimalUserBrainEntity( - id=brain_data["brains"]["id"], - name=brain_data["brains"]["name"], - rights=brain_data["rights"], - status=brain_data["brains"]["status"], - brain_type=brain_data["brains"]["brain_type"], - description=( - brain_data["brains"]["description"] - if brain_data["brains"]["description"] is not None - else "" - ), - integration_logo_url="", - max_files=100, - ) - - def delete_brain_user_by_id( - self, - user_id: UUID, - brain_id: UUID, - ): - results = ( - self.db.table("brains_users") - .delete() - .match({"brain_id": str(brain_id), "user_id": str(user_id)}) - .execute() - ) - return results.data - - def delete_brain_users(self, brain_id: str): - results = ( - self.db.table("brains_users") - .delete() - .match({"brain_id": brain_id}) - .execute() - ) - - return results - - def create_brain_user(self, user_id: UUID, brain_id, rights, default_brain: bool): - response = ( - self.db.table("brains_users") - .insert( - { - "brain_id": str(brain_id), - "user_id": str(user_id), - "rights": rights, - "default_brain": default_brain, - } - ) - .execute() - ) - return response - - def get_user_default_brain_id(self, user_id: UUID) -> UUID | None: - response = ( - self.db.from_("brains_users") - .select("brain_id") - .filter("user_id", "eq", user_id) - .filter("default_brain", "eq", True) - .execute() - ).data - if len(response) == 0: - return None - return UUID(response[0].get("brain_id")) - - def get_brain_users(self, brain_id: UUID) -> list[BrainUser]: - response = ( - self.db.table("brains_users") - .select("id:brain_id, *") - .filter("brain_id", "eq", str(brain_id)) - .execute() - ) - - return [BrainUser(**item) for item in response.data] - - def delete_brain_subscribers(self, brain_id: UUID): - results = ( - self.db.table("brains_users") - .delete() - .match({"brain_id": str(brain_id)}) - .match({"rights": "Viewer"}) - .execute() - ).data - - return results - - def get_brain_subscribers_count(self, brain_id: UUID) -> int: - response = ( - self.db.from_("brains_users") - .select( - "count", - ) - .filter("brain_id", "eq", str(brain_id)) - .execute() - ).data - if len(response) == 0: - raise ValueError(f"Brain with id {brain_id} does not exist.") - return response[0]["count"] - - def update_brain_user_default_status( - self, user_id: UUID, brain_id: UUID, default_brain: bool - ): - self.db.table("brains_users").update({"default_brain": default_brain}).match( - {"brain_id": brain_id, "user_id": user_id} - ).execute() - - def update_brain_user_rights( - self, user_id: UUID, brain_id: UUID, rights: str - ) -> None: - self.db.table("brains_users").update({"rights": rights}).match( - {"brain_id": brain_id, "user_id": user_id} - ).execute() diff --git a/backend/api/quivr_api/modules/brain/repository/brains_vectors.py b/backend/api/quivr_api/modules/brain/repository/brains_vectors.py deleted file mode 100644 index 1b1fe513a..000000000 --- a/backend/api/quivr_api/modules/brain/repository/brains_vectors.py +++ /dev/null @@ -1,139 +0,0 @@ -from uuid import UUID - -from sqlalchemy import text - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.repository.interfaces.brains_vectors_interface import ( - BrainsVectorsInterface, -) -from quivr_api.modules.dependencies import get_pg_database_engine, get_supabase_client - -logger = get_logger(__name__) - - -class BrainsVectors(BrainsVectorsInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - # FIXME(@aminediro) : refactor this to use session injected by Like other service - self.pg_engine = get_pg_database_engine() - - def create_brain_vector(self, brain_id, vector_id, file_sha1): - response = ( - self.db.table("brains_vectors") - .insert( - { - "brain_id": str(brain_id), - "vector_id": str(vector_id), - "file_sha1": file_sha1, - } - ) - .execute() - ) - return response.data - - def get_vector_ids_from_file_sha1(self, file_sha1: str): - # move to vectors class - vectorsResponse = ( - self.db.table("vectors") - .select("id") - .filter("file_sha1", "eq", file_sha1) - .execute() - ) - return vectorsResponse.data - - def get_unique_files_from_vector_ids(self, vectors_ids: list[UUID]): - pass - - def get_brain_size(self, brain_id: UUID) -> int: - query = """ - with - tmp_table as ( - select distinct - v.metadata ->> 'file_name' as file_name, - NULLIF(v.metadata ->> 'file_size', '')::int as file_size - from - vectors v, - brains_vectors bv - where - bv.brain_id = '{brain_id}'::uuid - and bv.vector_id = v.id - ) - select - sum(file_size) - from - tmp_table; - """ - with self.pg_engine.begin() as connection: - result = connection.execute(text(query.format(brain_id=brain_id))) - total_size = result.scalar() - - return int(total_size) if total_size is not None else -1 - - def get_brain_vector_ids(self, brain_id: UUID) -> list[UUID]: - """ - Retrieve unique brain data (i.e. uploaded files and crawled websites). - """ - - response = ( - self.db.from_("brains_vectors") - .select("vector_id") - .filter("brain_id", "eq", str(brain_id)) - .execute() - ) - - vector_ids = [UUID(item["vector_id"]) for item in response.data] - - if len(vector_ids) == 0: - return [] - - return vector_ids - - def delete_file_from_brain(self, brain_id, file_name: str): - # First, get the vector_ids associated with the file_name - # TODO: filter by brain_id - file_vectors = ( - self.db.table("vectors") - .select("id") - .filter("metadata->>file_name", "eq", file_name) - .execute() - ) - - file_vectors_ids = [item["id"] for item in file_vectors.data] - - # remove current file vectors from brain vectors - self.db.table("brains_vectors").delete().filter( - "vector_id", "in", f"({','.join(map(str, file_vectors_ids))})" - ).filter("brain_id", "eq", brain_id).execute() - - vectors_used_by_another_brain = ( - self.db.table("brains_vectors") - .select("vector_id") - .filter("vector_id", "in", f"({','.join(map(str, file_vectors_ids))})") - .filter("brain_id", "neq", brain_id) - .execute() - ) - - vectors_used_by_another_brain_ids = [ - item["vector_id"] for item in vectors_used_by_another_brain.data - ] - - vectors_no_longer_used_ids = [ - id for id in file_vectors_ids if id not in vectors_used_by_another_brain_ids - ] - - self.db.table("vectors").delete().filter( - "id", "in", f"({','.join(map(str, vectors_no_longer_used_ids))})" - ).execute() - - return {"message": f"File {file_name} in brain {brain_id} has been deleted."} - - def delete_brain_vector(self, brain_id: str): - results = ( - self.db.table("brains_vectors") - .delete() - .match({"brain_id": brain_id}) - .execute() - ) - - return results diff --git a/backend/api/quivr_api/modules/brain/repository/integration_brains.py b/backend/api/quivr_api/modules/brain/repository/integration_brains.py deleted file mode 100644 index df1f0c475..000000000 --- a/backend/api/quivr_api/modules/brain/repository/integration_brains.py +++ /dev/null @@ -1,148 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List - -from quivr_api.modules.brain.entity.integration_brain import ( - IntegrationDescriptionEntity, - IntegrationEntity, -) -from quivr_api.modules.brain.repository.interfaces.integration_brains_interface import ( - IntegrationBrainInterface, - IntegrationDescriptionInterface, -) -from quivr_api.modules.dependencies import get_supabase_client - - -class Integration(ABC): - @abstractmethod - def load(self): - pass - - @abstractmethod - def poll(self): - pass - - -class IntegrationBrain(IntegrationBrainInterface): - """This is all the methods to interact with the integration brain. - - Args: - IntegrationBrainInterface (_type_): _description_ - """ - - def __init__(self): - self.db = get_supabase_client() - - def get_integration_brain(self, brain_id, user_id=None): - query = ( - self.db.table("integrations_user") - .select("*") - .filter("brain_id", "eq", brain_id) - ) - - if user_id: - query.filter("user_id", "eq", user_id) - - response = query.execute() - - if len(response.data) == 0: - return None - - return IntegrationEntity(**response.data[0]) - - def update_last_synced(self, brain_id, user_id): - response = ( - self.db.table("integrations_user") - .update({"last_synced": "now()"}) - .filter("brain_id", "eq", str(brain_id)) - .filter("user_id", "eq", str(user_id)) - .execute() - ) - if len(response.data) == 0: - return None - return IntegrationEntity(**response.data[0]) - - def add_integration_brain(self, brain_id, user_id, integration_id, settings): - response = ( - self.db.table("integrations_user") - .insert( - [ - { - "brain_id": str(brain_id), - "user_id": str(user_id), - "integration_id": str(integration_id), - "settings": settings, - } - ] - ) - .execute() - ) - if len(response.data) == 0: - return None - return IntegrationEntity(**response.data[0]) - - def update_integration_brain(self, brain_id, user_id, integration_brain): - response = ( - self.db.table("integrations_user") - .update(integration_brain.dict(exclude={"brain_id", "user_id"})) - .filter("brain_id", "eq", str(brain_id)) - .filter("user_id", "eq", str(user_id)) - .execute() - ) - if len(response.data) == 0: - return None - return IntegrationEntity(**response.data[0]) - - def delete_integration_brain(self, brain_id, user_id): - self.db.table("integrations_user").delete().filter( - "brain_id", "eq", str(brain_id) - ).filter("user_id", "eq", str(user_id)).execute() - return None - - def get_integration_brain_by_type_integration( - self, integration_name - ) -> List[IntegrationEntity] | None: - response = ( - self.db.table("integrations_user") - .select("*, integrations ()") - .filter("integrations.integration_name", "eq", integration_name) - .execute() - ) - if len(response.data) == 0: - return None - - return [IntegrationEntity(**data) for data in response.data] - - -class IntegrationDescription(IntegrationDescriptionInterface): - def __init__(self): - self.db = get_supabase_client() - - def get_integration_description(self, integration_id): - response = ( - self.db.table("integrations") - .select("*") - .filter("id", "eq", integration_id) - .execute() - ) - if len(response.data) == 0: - return None - - return IntegrationDescriptionEntity(**response.data[0]) - - def get_integration_description_by_user_brain_id(self, brain_id, user_id): - response = ( - self.db.table("integrations_user") - .select("*") - .filter("brain_id", "eq", brain_id) - .filter("user_id", "eq", user_id) - .execute() - ) - if len(response.data) == 0: - return None - - integration_id = response.data[0]["integration_id"] - return self.get_integration_description(integration_id) - - def get_all_integration_descriptions(self): - response = self.db.table("integrations").select("*").execute() - return [IntegrationDescriptionEntity(**data) for data in response.data] diff --git a/backend/api/quivr_api/modules/brain/repository/interfaces/__init__.py b/backend/api/quivr_api/modules/brain/repository/interfaces/__init__.py deleted file mode 100644 index 15163d6c6..000000000 --- a/backend/api/quivr_api/modules/brain/repository/interfaces/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# noqa -from .brains_interface import BrainsInterface -from .brains_users_interface import BrainsUsersInterface -from .brains_vectors_interface import BrainsVectorsInterface -from .integration_brains_interface import ( - IntegrationBrainInterface, - IntegrationDescriptionInterface, -) diff --git a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_interface.py b/backend/api/quivr_api/modules/brain/repository/interfaces/brains_interface.py deleted file mode 100644 index 714807649..000000000 --- a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_interface.py +++ /dev/null @@ -1,54 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import UUID - -from quivr_api.modules.brain.dto.inputs import ( - BrainUpdatableProperties, - CreateBrainProperties, -) -from quivr_api.modules.brain.entity.brain_entity import BrainEntity - - -class BrainsInterface(ABC): - @abstractmethod - def create_brain(self, brain: CreateBrainProperties) -> BrainEntity: - """ - Create a brain in the brains table - """ - pass - - @abstractmethod - def get_brain_details(self, brain_id: UUID, user_id: UUID) -> BrainEntity | None: - """ - Get all public brains - """ - pass - - @abstractmethod - def update_brain_last_update_time(self, brain_id: UUID) -> None: - """ - Update the last update time of the brain - """ - pass - - @abstractmethod - def delete_brain(self, brain_id: UUID): - """ - Delete a brain - """ - pass - - @abstractmethod - def update_brain_by_id( - self, brain_id: UUID, brain: BrainUpdatableProperties - ) -> BrainEntity | None: - """ - Update a brain by id - """ - pass - - @abstractmethod - def get_brain_by_id(self, brain_id: UUID) -> BrainEntity | None: - """ - Get a brain by id - """ - pass diff --git a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_users_interface.py b/backend/api/quivr_api/modules/brain/repository/interfaces/brains_users_interface.py deleted file mode 100644 index dabe8ef92..000000000 --- a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_users_interface.py +++ /dev/null @@ -1,95 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List -from uuid import UUID - -from quivr_api.modules.brain.entity.brain_entity import ( - BrainUser, - MinimalUserBrainEntity, -) - - -class BrainsUsersInterface(ABC): - @abstractmethod - def get_user_brains(self, user_id) -> list[MinimalUserBrainEntity]: - """ - Create a brain in the brains table - """ - pass - - @abstractmethod - def get_brain_for_user(self, user_id, brain_id) -> MinimalUserBrainEntity | None: - """ - Get a brain for a user - """ - pass - - @abstractmethod - def delete_brain_user_by_id( - self, - user_id: UUID, - brain_id: UUID, - ): - """ - Delete a user in a brain - """ - pass - - @abstractmethod - def delete_brain_users(self, brain_id: str): - """ - Delete all users for a brain - """ - pass - - @abstractmethod - def create_brain_user(self, user_id: UUID, brain_id, rights, default_brain: bool): - """ - Create a brain user - """ - pass - - @abstractmethod - def get_user_default_brain_id(self, user_id: UUID) -> UUID | None: - """ - Get the default brain id for a user - """ - pass - - @abstractmethod - def get_brain_users(self, brain_id: UUID) -> List[BrainUser]: - """ - Get all users for a brain - """ - pass - - @abstractmethod - def delete_brain_subscribers(self, brain_id: UUID): - """ - Delete all subscribers for a brain with Viewer rights - """ - pass - - @abstractmethod - def get_brain_subscribers_count(self, brain_id: UUID) -> int: - """ - Get the number of subscribers for a brain - """ - pass - - @abstractmethod - def update_brain_user_default_status( - self, user_id: UUID, brain_id: UUID, default_brain: bool - ): - """ - Update the default brain status for a user - """ - pass - - @abstractmethod - def update_brain_user_rights( - self, brain_id: UUID, user_id: UUID, rights: str - ) -> BrainUser: - """ - Update the rights for a user in a brain - """ - pass diff --git a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_vectors_interface.py b/backend/api/quivr_api/modules/brain/repository/interfaces/brains_vectors_interface.py deleted file mode 100644 index 35d0e2729..000000000 --- a/backend/api/quivr_api/modules/brain/repository/interfaces/brains_vectors_interface.py +++ /dev/null @@ -1,41 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List -from uuid import UUID - - -# TODO: Replace BrainsVectors with KnowledgeVectors interface instead -class BrainsVectorsInterface(ABC): - @abstractmethod - def create_brain_vector(self, brain_id, vector_id, file_sha1): - """ - Create a brain vector - """ - pass - - @abstractmethod - def get_vector_ids_from_file_sha1(self, file_sha1: str): - """ - Get vector ids from file sha1 - """ - pass - - @abstractmethod - def get_brain_vector_ids(self, brain_id) -> List[UUID]: - """ - Get brain vector ids - """ - pass - - @abstractmethod - def delete_file_from_brain(self, brain_id, file_name: str): - """ - Delete file from brain - """ - pass - - @abstractmethod - def delete_brain_vector(self, brain_id: str): - """ - Delete brain vector - """ - pass diff --git a/backend/api/quivr_api/modules/brain/repository/interfaces/integration_brains_interface.py b/backend/api/quivr_api/modules/brain/repository/interfaces/integration_brains_interface.py deleted file mode 100644 index 8f6867514..000000000 --- a/backend/api/quivr_api/modules/brain/repository/interfaces/integration_brains_interface.py +++ /dev/null @@ -1,63 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import UUID - -from quivr_api.modules.brain.entity.integration_brain import ( - IntegrationDescriptionEntity, - IntegrationEntity, -) - - -class IntegrationBrainInterface(ABC): - @abstractmethod - def get_integration_brain(self, brain_id: UUID) -> IntegrationEntity: - """Get the integration brain entity - - Args: - brain_id (UUID): ID of the brain - - Returns: - IntegrationEntity: Integration brain entity - """ - pass - - @abstractmethod - def add_integration_brain( - self, brain_id: UUID, integration_brain: IntegrationEntity - ) -> IntegrationEntity: - pass - - @abstractmethod - def update_integration_brain( - self, brain_id: UUID, integration_brain: IntegrationEntity - ) -> IntegrationEntity: - pass - - @abstractmethod - def delete_integration_brain(self, brain_id: UUID) -> None: - pass - - -class IntegrationDescriptionInterface(ABC): - @abstractmethod - def get_integration_description( - self, integration_id: UUID - ) -> IntegrationDescriptionEntity: - """Get the integration description entity - - Args: - integration_id (UUID): ID of the integration - - Returns: - IntegrationEntity: Integration description entity - """ - pass - - @abstractmethod - def get_all_integration_descriptions(self) -> list[IntegrationDescriptionEntity]: - pass - - @abstractmethod - def get_integration_description_by_user_brain_id( - self, brain_id: UUID, user_id: UUID - ) -> IntegrationDescriptionEntity: - pass diff --git a/backend/api/quivr_api/modules/brain/service/__init__.py b/backend/api/quivr_api/modules/brain/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/service/brain_authorization_service.py b/backend/api/quivr_api/modules/brain/service/brain_authorization_service.py deleted file mode 100644 index 9583c1239..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_authorization_service.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import List, Optional, Union -from uuid import UUID - -from fastapi import Depends, HTTPException, status - -from quivr_api.middlewares.auth.auth_bearer import get_current_user -from quivr_api.modules.brain.entity.brain_entity import RoleEnum -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -brain_user_service = BrainUserService() -brain_service = BrainService() - - -def has_brain_authorization( - required_roles: Optional[Union[RoleEnum, List[RoleEnum]]] = RoleEnum.Owner, -): - """ - Decorator to check if the user has the required role(s) for the brain - param: required_roles: The role(s) required to access the brain - return: A wrapper function that checks the authorization - """ - - async def wrapper( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) - ): - nonlocal required_roles - if isinstance(required_roles, str): - required_roles = [required_roles] # Convert single role to a list - validate_brain_authorization( - brain_id=brain_id, user_id=current_user.id, required_roles=required_roles - ) - - return wrapper - - -def validate_brain_authorization( - brain_id: UUID, - user_id: UUID, - required_roles: Optional[Union[RoleEnum, List[RoleEnum]]] = RoleEnum.Owner, -): - """ - Function to check if the user has the required role(s) for the brain - param: brain_id: The id of the brain - param: user_id: The id of the user - param: required_roles: The role(s) required to access the brain - return: None - """ - - brain = brain_service.get_brain_details(brain_id, user_id) - - if brain and brain.status == "public": - return - - if required_roles is None: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Missing required role", - ) - - user_brain = brain_user_service.get_brain_for_user(user_id, brain_id) - if user_brain is None: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You don't have permission for this brain", - ) - - # Convert single role to a list to handle both cases - if isinstance(required_roles, str): - required_roles = [required_roles] - - # Check if the user has at least one of the required roles - if user_brain.rights not in required_roles: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You don't have the required role(s) for this brain", - ) diff --git a/backend/api/quivr_api/modules/brain/service/brain_service.py b/backend/api/quivr_api/modules/brain/service/brain_service.py deleted file mode 100644 index 7b9da881c..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_service.py +++ /dev/null @@ -1,228 +0,0 @@ -from typing import Dict, Optional, Tuple -from uuid import UUID - -from fastapi import HTTPException - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.modules.brain.dto.inputs import ( - BrainUpdatableProperties, - CreateBrainProperties, -) -from quivr_api.modules.brain.entity.brain_entity import BrainEntity, BrainType -from quivr_api.modules.brain.entity.integration_brain import IntegrationEntity -from quivr_api.modules.brain.repository import ( - Brains, - BrainsUsers, - BrainsVectors, - IntegrationBrain, - IntegrationDescription, -) -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.vectorstore.supabase import CustomSupabaseVectorStore - -logger = get_logger(__name__) -knowledge_service = get_service(KnowledgeService)() - - -class BrainService: - # brain_repository: BrainsInterface - # brain_user_repository: BrainsUsersInterface - # brain_vector_repository: BrainsVectorsInterface - # integration_brains_repository: IntegrationBrainInterface - # integration_description_repository: IntegrationDescriptionInterface - - def __init__(self): - self.brain_repository: Brains = Brains() - self.brain_user_repository = BrainsUsers() - self.brain_vector = BrainsVectors() - self.integration_brains_repository = IntegrationBrain() - self.integration_description_repository = IntegrationDescription() - - def get_brain_by_id(self, brain_id: UUID): - return self.brain_repository.get_brain_by_id(brain_id) - - def get_integration_brain(self, brain_id) -> IntegrationEntity | None: - return self.integration_brains_repository.get_integration_brain(brain_id) - - def find_brain_from_question( - self, - brain_id: UUID, - question: str, - user, - chat_id: UUID, - history, - vector_store: CustomSupabaseVectorStore, - ) -> Tuple[Optional[BrainEntity], Dict[str, str]]: - """Find the brain to use for a question. - - Args: - brain_id (UUID): ID of the brain to use if exists - question (str): Question for which to find the brain - user (UserEntity): User asking the question - chat_id (UUID): ID of the chat - - Returns: - Optional[BrainEntity]: Returns the brain to use for the question - """ - metadata = {} - - # Init - - brain_id_to_use = brain_id - brain_to_use = None - - # Get the first question from the chat_question - - question = question - - list_brains = [] # To return - - if history and not brain_id_to_use: - question = history[0].user_message - brain_id_to_use = history[0].brain_id - brain_to_use = self.get_brain_by_id(brain_id_to_use) - - # If a brain_id is provided, use it - if brain_id_to_use and not brain_to_use: - brain_to_use = self.get_brain_by_id(brain_id_to_use) - - else: - # Calculate the closest brains to the question - list_brains = vector_store.find_brain_closest_query(user.id, question) - - unique_list_brains = [] - seen_brain_ids = set() - - for brain in list_brains: - if brain["id"] not in seen_brain_ids: - unique_list_brains.append(brain) - seen_brain_ids.add(brain["id"]) - - metadata["close_brains"] = unique_list_brains[:5] - - if list_brains and not brain_to_use: - brain_id_to_use = list_brains[0]["id"] - brain_to_use = self.get_brain_by_id(brain_id_to_use) - - return brain_to_use, metadata # type: ignore - - def create_brain( - self, - user_id: UUID, - brain: CreateBrainProperties | None = None, - ) -> BrainEntity: - if brain is None: - brain = CreateBrainProperties() - - if brain.brain_type == BrainType.integration: - return self.create_brain_integration(user_id, brain) - - created_brain = self.brain_repository.create_brain(brain) - return created_brain - - def create_brain_integration( - self, - user_id: UUID, - brain: CreateBrainProperties, - ) -> BrainEntity: - created_brain = self.brain_repository.create_brain(brain) - if brain.integration is not None: - self.integration_brains_repository.add_integration_brain( - user_id=user_id, - brain_id=created_brain.brain_id, - integration_id=brain.integration.integration_id, - settings=brain.integration.settings, - ) - if ( - self.integration_description_repository.get_integration_description( - brain.integration.integration_id - ).integration_name.lower() - == "notion" - ): - celery.send_task( - "NotionConnectorLoad", - kwargs={"brain_id": created_brain.brain_id, "user_id": user_id}, - ) - return created_brain - - def delete_brain(self, brain_id: UUID) -> dict[str, str]: - brain_to_delete = self.get_brain_by_id(brain_id=brain_id) - if brain_to_delete is None: - raise HTTPException(status_code=404, detail="Brain not found.") - - # knowledge_service.remove_brain_all_knowledge(brain_id) #FIXME we don't really want to delete the knowledge @amine if a knowledge can be in multiple brain - - self.brain_vector.delete_brain_vector(str(brain_id)) - self.brain_user_repository.delete_brain_users(str(brain_id)) - self.brain_repository.delete_brain(str(brain_id)) # type: ignore - - return {"message": "Brain deleted."} - - def get_brain_prompt_id(self, brain_id: UUID) -> UUID | None: - brain = self.get_brain_by_id(brain_id) - prompt_id = brain.prompt_id if brain else None - - return prompt_id - - def update_brain_by_id( - self, brain_id: UUID, brain_new_values: BrainUpdatableProperties - ) -> BrainEntity: - """Update a prompt by id""" - - existing_brain = self.brain_repository.get_brain_by_id(brain_id) - - if existing_brain is None: - raise HTTPException( - status_code=404, - detail=f"Brain with id {brain_id} not found", - ) - brain_update_answer = self.brain_repository.update_brain_by_id( - brain_id, - brain=BrainUpdatableProperties( - **brain_new_values.dict(exclude={"integration"}) - ), - ) - - if brain_update_answer is None: - raise HTTPException( - status_code=404, - detail=f"Brain with id {brain_id} not found", - ) - - if brain_update_answer is None: - raise HTTPException( - status_code=404, - detail=f"Brain with id {brain_id} not found", - ) - - self.brain_repository.update_brain_last_update_time(brain_id) - return brain_update_answer - - def update_brain_last_update_time(self, brain_id: UUID): - self.brain_repository.update_brain_last_update_time(brain_id) - - def get_brain_details( - self, brain_id: UUID, user_id: UUID | None = None - ) -> BrainEntity | None: - brain = self.brain_repository.get_brain_details(brain_id) - if brain is None: - return None - - # TODO: N+1 here !! - if brain.brain_type == BrainType.integration: - brain.integration = ( - self.integration_brains_repository.get_integration_brain( - brain_id, user_id - ) - ) - - if brain.integration: - brain.integration_description = ( - self.integration_description_repository.get_integration_description( - brain.integration.integration_id - ) - ) - - return brain diff --git a/backend/api/quivr_api/modules/brain/service/brain_subscription/__init__.py b/backend/api/quivr_api/modules/brain/service/brain_subscription/__init__.py deleted file mode 100644 index efe9797fc..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_subscription/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .resend_invitation_email import resend_invitation_email -from .subscription_invitation_service import SubscriptionInvitationService diff --git a/backend/api/quivr_api/modules/brain/service/brain_subscription/resend_invitation_email.py b/backend/api/quivr_api/modules/brain/service/brain_subscription/resend_invitation_email.py deleted file mode 100644 index 969b8f1ea..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_subscription/resend_invitation_email.py +++ /dev/null @@ -1,57 +0,0 @@ -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.models.brains_subscription_invitations import BrainSubscription -from quivr_api.models.settings import BrainSettings -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.utils.send_email import send_email - -logger = get_logger(__name__) - -brain_service = BrainService() - - -def get_brain_url(origin: str, brain_id: UUID) -> str: - """Generates the brain URL based on the brain_id.""" - - return f"{origin}/invitation/{brain_id}" - - -def resend_invitation_email( - brain_subscription: BrainSubscription, - inviter_email: str, - user_id: UUID, - origin: str = "https://chat.quivr.app", -): - brains_settings = BrainSettings() # pyright: ignore reportPrivateUsage=none - - brain_url = get_brain_url(origin, brain_subscription.brain_id) - - invitation_brain = brain_service.get_brain_details( - brain_subscription.brain_id, user_id - ) - if invitation_brain is None: - raise Exception("Brain not found") - brain_name = invitation_brain.name - - html_body = f""" -

Brain {brain_name} has been shared with you by {inviter_email}.

-

Click here to access your brain.

- """ - - try: - r = send_email( - { - "from": brains_settings.resend_email_address, - "to": [brain_subscription.email], - "subject": "Quivr - Brain Shared With You", - "reply_to": "no-reply@quivr.app", - "html": html_body, - } - ) - logger.info("Resend response", r) - except Exception as e: - logger.error(f"Error sending email: {e}") - return - - return r diff --git a/backend/api/quivr_api/modules/brain/service/brain_subscription/subscription_invitation_service.py b/backend/api/quivr_api/modules/brain/service/brain_subscription/subscription_invitation_service.py deleted file mode 100644 index ea1609a2d..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_subscription/subscription_invitation_service.py +++ /dev/null @@ -1,106 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.models.brains_subscription_invitations import BrainSubscription -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.user.service.user_service import UserService - -logger = get_logger(__name__) - - -brain_user_service = BrainUserService() -user_service = UserService() - - -class SubscriptionInvitationService: - def __init__(self): - self.supabase_client = get_supabase_client() - - def create_subscription_invitation(self, brain_subscription: BrainSubscription): - logger.info("Creating subscription invitation") - response = ( - self.supabase_client.table("brain_subscription_invitations") - .insert( - { - "brain_id": str(brain_subscription.brain_id), - "email": brain_subscription.email, - "rights": brain_subscription.rights, - } - ) - .execute() - ) - return response.data - - def update_subscription_invitation(self, brain_subscription: BrainSubscription): - logger.info("Updating subscription invitation") - response = ( - self.supabase_client.table("brain_subscription_invitations") - .update({"rights": brain_subscription.rights}) - .eq("brain_id", str(brain_subscription.brain_id)) - .eq("email", brain_subscription.email) - .execute() - ) - return response.data - - def create_or_update_subscription_invitation( - self, - brain_subscription: BrainSubscription, - ) -> bool: - """ - Creates a subscription invitation if it does not exist, otherwise updates it. - Returns True if the invitation was created or updated and False if user already has access. - """ - response = ( - self.supabase_client.table("brain_subscription_invitations") - .select("*") - .eq("brain_id", str(brain_subscription.brain_id)) - .eq("email", brain_subscription.email) - .execute() - ) - - if response.data: - self.update_subscription_invitation(brain_subscription) - return True - else: - user_id = user_service.get_user_id_by_email(brain_subscription.email) - brain_user = None - - if user_id is not None: - brain_id = brain_subscription.brain_id - brain_user = brain_user_service.get_brain_for_user(user_id, brain_id) - - if brain_user is None: - self.create_subscription_invitation(brain_subscription) - return True - - return False - - def fetch_invitation(self, subscription: BrainSubscription): - logger.info("Fetching subscription invitation") - response = ( - self.supabase_client.table("brain_subscription_invitations") - .select("*") - .eq("brain_id", str(subscription.brain_id)) - .eq("email", subscription.email) - .execute() - ) - if response.data: - return response.data[0] # return the first matching invitation - else: - return None - - def remove_invitation(self, subscription: BrainSubscription): - logger.info( - f"Removing subscription invitation for email {subscription.email} and brain {subscription.brain_id}" - ) - response = ( - self.supabase_client.table("brain_subscription_invitations") - .delete() - .eq("brain_id", str(subscription.brain_id)) - .eq("email", subscription.email) - .execute() - ) - logger.info( - f"Removed subscription invitation for email {subscription.email} and brain {subscription.brain_id}" - ) - logger.info(response) - return response.data diff --git a/backend/api/quivr_api/modules/brain/service/brain_user_service.py b/backend/api/quivr_api/modules/brain/service/brain_user_service.py deleted file mode 100644 index 031cfb8a3..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_user_service.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import List -from uuid import UUID - -from fastapi import HTTPException - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import ( - BrainEntity, - BrainUser, - MinimalUserBrainEntity, - RoleEnum, -) -from quivr_api.modules.brain.repository.brains import Brains -from quivr_api.modules.brain.repository.brains_users import BrainsUsers -from quivr_api.modules.brain.repository.interfaces.brains_interface import ( - BrainsInterface, -) -from quivr_api.modules.brain.repository.interfaces.brains_users_interface import ( - BrainsUsersInterface, -) -from quivr_api.modules.brain.service.brain_service import BrainService - -logger = get_logger(__name__) - -brain_service = BrainService() - - -class BrainUserService: - brain_repository: BrainsInterface - brain_user_repository: BrainsUsersInterface - - def __init__(self): - self.brain_repository = Brains() - self.brain_user_repository = BrainsUsers() - - def get_user_default_brain(self, user_id: UUID) -> BrainEntity | None: - brain_id = self.brain_user_repository.get_user_default_brain_id(user_id) - - if brain_id is None: - return None - - return brain_service.get_brain_by_id(brain_id) - - def delete_brain_user(self, user_id: UUID, brain_id: UUID) -> None: - brain_to_delete_user_from = brain_service.get_brain_by_id(brain_id=brain_id) - if brain_to_delete_user_from is None: - raise HTTPException(status_code=404, detail="Brain not found.") - - self.brain_user_repository.delete_brain_user_by_id( - user_id=user_id, - brain_id=brain_id, - ) - - def delete_brain_users(self, brain_id: UUID) -> None: - self.brain_user_repository.delete_brain_subscribers( - brain_id=brain_id, - ) - - def create_brain_user( - self, user_id: UUID, brain_id: UUID, rights: RoleEnum, is_default_brain: bool - ): - self.brain_user_repository.create_brain_user( - user_id=user_id, - brain_id=brain_id, - rights=rights, - default_brain=is_default_brain, - ) - - def get_brain_for_user(self, user_id: UUID, brain_id: UUID): - return self.brain_user_repository.get_brain_for_user(user_id, brain_id) # type: ignore - - def get_user_brains(self, user_id: UUID) -> list[MinimalUserBrainEntity]: - results = self.brain_user_repository.get_user_brains(user_id) # type: ignore - - return results # type: ignore - - def get_brain_users(self, brain_id: UUID) -> List[BrainUser]: - return self.brain_user_repository.get_brain_users(brain_id) - - def update_brain_user_rights( - self, brain_id: UUID, user_id: UUID, rights: str - ) -> None: - self.brain_user_repository.update_brain_user_rights( - brain_id=brain_id, - user_id=user_id, - rights=rights, - ) diff --git a/backend/api/quivr_api/modules/brain/service/brain_vector_service.py b/backend/api/quivr_api/modules/brain/service/brain_vector_service.py deleted file mode 100644 index ec514cdd7..000000000 --- a/backend/api/quivr_api/modules/brain/service/brain_vector_service.py +++ /dev/null @@ -1,40 +0,0 @@ -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.repository.brains_vectors import BrainsVectors -from quivr_api.modules.knowledge.repository.storage import SupabaseS3Storage - -logger = get_logger(__name__) - - -class BrainVectorService: - def __init__(self, brain_id: UUID): - self.repository = BrainsVectors() - self.brain_id = brain_id - self.storage = SupabaseS3Storage() - - def create_brain_vector(self, vector_id: str, file_sha1: str): - return self.repository.create_brain_vector(self.brain_id, vector_id, file_sha1) # type: ignore - - def update_brain_with_file(self, file_sha1: str): - # not used - vector_ids = self.repository.get_vector_ids_from_file_sha1(file_sha1) - if vector_ids is None or len(vector_ids) == 0: - logger.info(f"No vector ids found for file {file_sha1}") - return - - for vector_id in vector_ids: - self.create_brain_vector(vector_id, file_sha1) - - async def delete_file_from_brain(self, file_name: str, only_vectors: bool = False): - file_name_with_brain_id = f"{self.brain_id}/{file_name}" - if not only_vectors: - await self.storage.remove_file(file_name_with_brain_id) - return self.repository.delete_file_from_brain(self.brain_id, file_name) # type: ignore - - def delete_file_url_from_brain(self, file_name: str): - return self.repository.delete_file_from_brain(self.brain_id, file_name) # type: ignore - - @property - def brain_size(self): - return self.repository.get_brain_size(self.brain_id) diff --git a/backend/api/quivr_api/modules/brain/service/get_question_context_from_brain.py b/backend/api/quivr_api/modules/brain/service/get_question_context_from_brain.py deleted file mode 100644 index 5be512097..000000000 --- a/backend/api/quivr_api/modules/brain/service/get_question_context_from_brain.py +++ /dev/null @@ -1,67 +0,0 @@ -from uuid import UUID - -from attr import dataclass - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import get_embedding_client, get_supabase_client -from quivr_api.modules.upload.service.generate_file_signed_url import ( - generate_file_signed_url, -) -from quivr_api.vectorstore.supabase import CustomSupabaseVectorStore - -logger = get_logger(__name__) - - -@dataclass -class DocumentAnswer: - file_name: str - file_sha1: str - file_size: int - file_url: str = "" - file_id: str = "" - file_similarity: float = 0.0 - - -def get_question_context_from_brain(brain_id: UUID, question: str) -> str: - """Finds the best brain to answer the question based on the question's meaning. - - Args: - brain_id (UUID): Id of the brain to search in - question (str): Question to search for in the vector store - - Returns: - str: _descripton_ - """ - # TODO: Move to AnswerGenerator service - supabase_client = get_supabase_client() - embeddings = get_embedding_client() - - vector_store = CustomSupabaseVectorStore( - supabase_client, - embeddings, - table_name="vectors", - brain_id=str(brain_id), - number_docs=20, - ) - documents = vector_store.similarity_search(question, k=20, threshold=0.8) - - answers = [] - file_sha1s = [] - for document in documents: - if document.metadata["file_sha1"] not in file_sha1s: - file_sha1s.append(document.metadata["file_sha1"]) - file_path_in_storage = f"{brain_id}/{document.metadata['file_name']}" - answers.append( - DocumentAnswer( - file_name=document.metadata["file_name"], - file_sha1=document.metadata["file_sha1"], - file_size=document.metadata["file_size"], - file_id=document.metadata["id"], - file_similarity=document.metadata["similarity"], - file_url=generate_file_signed_url(file_path_in_storage).get( - "signedURL", "" - ), - ), - ) - - return answers diff --git a/backend/api/quivr_api/modules/brain/service/integration_brain_service.py b/backend/api/quivr_api/modules/brain/service/integration_brain_service.py deleted file mode 100644 index f8aa1be94..000000000 --- a/backend/api/quivr_api/modules/brain/service/integration_brain_service.py +++ /dev/null @@ -1,29 +0,0 @@ -from quivr_api.modules.brain.entity.integration_brain import ( - IntegrationDescriptionEntity, -) -from quivr_api.modules.brain.repository.integration_brains import IntegrationDescription -from quivr_api.modules.brain.repository.interfaces import ( - IntegrationDescriptionInterface, -) - - -class IntegrationBrainDescriptionService: - repository: IntegrationDescriptionInterface - - def __init__(self): - self.repository = IntegrationDescription() - - def get_all_integration_descriptions(self) -> list[IntegrationDescriptionEntity]: - return self.repository.get_all_integration_descriptions() - - def get_integration_description( - self, integration_id - ) -> IntegrationDescriptionEntity: - return self.repository.get_integration_description(integration_id) - - def get_integration_description_by_user_brain_id( - self, brain_id, user_id - ) -> IntegrationDescriptionEntity: - return self.repository.get_integration_description_by_user_brain_id( - brain_id, user_id - ) diff --git a/backend/api/quivr_api/modules/brain/service/test_brain_service.py b/backend/api/quivr_api/modules/brain/service/test_brain_service.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/service/utils/__init__.py b/backend/api/quivr_api/modules/brain/service/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/service/utils/format_chat_history.py b/backend/api/quivr_api/modules/brain/service/utils/format_chat_history.py deleted file mode 100644 index 0b3d3c795..000000000 --- a/backend/api/quivr_api/modules/brain/service/utils/format_chat_history.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import List, Tuple - -from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage - -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput - - -def format_chat_history( - history: List[GetChatHistoryOutput], -) -> List[HumanMessage | AIMessage]: - """Format the chat history into a list of HumanMessage and AIMessage""" - formatted_history = [] - for chat in history: - if chat.user_message: - formatted_history.append(HumanMessage(content=chat.user_message)) - if chat.assistant: - formatted_history.append(AIMessage(content=chat.assistant)) - return formatted_history - - -def format_history_to_openai_mesages( - tuple_history: List[Tuple[str, str]], system_message: str, question: str -) -> List[BaseMessage]: - """Format the chat history into a list of Base Messages""" - messages = [] - messages.append(SystemMessage(content=system_message)) - for human, ai in tuple_history: - messages.append(HumanMessage(content=human)) - messages.append(AIMessage(content=ai)) - messages.append(HumanMessage(content=question)) - return messages diff --git a/backend/api/quivr_api/modules/brain/service/utils/get_prompt_to_use_id.py b/backend/api/quivr_api/modules/brain/service/utils/get_prompt_to_use_id.py deleted file mode 100644 index e5d118bf0..000000000 --- a/backend/api/quivr_api/modules/brain/service/utils/get_prompt_to_use_id.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Optional -from uuid import UUID - -from quivr_api.modules.brain.service.brain_service import BrainService - -brain_service = BrainService() - - -def get_prompt_to_use_id( - brain_id: Optional[UUID], prompt_id: Optional[UUID] -) -> Optional[UUID]: - if brain_id is None and prompt_id is None: - return None - - return ( - prompt_id - if prompt_id - else brain_service.get_brain_prompt_id(brain_id) - if brain_id - else None - ) diff --git a/backend/api/quivr_api/modules/brain/service/utils/validate_brain.py b/backend/api/quivr_api/modules/brain/service/utils/validate_brain.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/brain/tests/test_brains_interface.py b/backend/api/quivr_api/modules/brain/tests/test_brains_interface.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/__init__.py b/backend/api/quivr_api/modules/chat/__init__.py deleted file mode 100644 index ed9f136d2..000000000 --- a/backend/api/quivr_api/modules/chat/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# noqa: -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.prompt.entity.prompt import Prompt -from quivr_api.modules.user.entity.user_identity import User - -__all__ = ["Brain", "User", "Prompt"] diff --git a/backend/api/quivr_api/modules/chat/controller/__init__.py b/backend/api/quivr_api/modules/chat/controller/__init__.py deleted file mode 100644 index 0ec06ba92..000000000 --- a/backend/api/quivr_api/modules/chat/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .chat_routes import chat_router diff --git a/backend/api/quivr_api/modules/chat/controller/chat/__init_.py b/backend/api/quivr_api/modules/chat/controller/chat/__init_.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/controller/chat/interface.py b/backend/api/quivr_api/modules/chat/controller/chat/interface.py deleted file mode 100644 index a0bbc95c8..000000000 --- a/backend/api/quivr_api/modules/chat/controller/chat/interface.py +++ /dev/null @@ -1,17 +0,0 @@ -from abc import ABC, abstractmethod - - -class ChatInterface(ABC): - @abstractmethod - def get_answer_generator( - self, - chat_id, - model, - max_tokens, - temperature, - streaming, - prompt_id, - user_id, - chat_question, - ): - pass diff --git a/backend/api/quivr_api/modules/chat/controller/chat/test_utils.py b/backend/api/quivr_api/modules/chat/controller/chat/test_utils.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/controller/chat/utils.py b/backend/api/quivr_api/modules/chat/controller/chat/utils.py deleted file mode 100644 index ce5e68422..000000000 --- a/backend/api/quivr_api/modules/chat/controller/chat/utils.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -import time -from enum import Enum - -from fastapi import HTTPException -from quivr_api.logger import get_logger -from quivr_api.modules.models.entity.model import Model -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_usage import UserUsage -from quivr_core.config import RetrievalConfig - -logger = get_logger(__name__) - - -class RetrievalConfigPathEnv(Enum): - CHAT_WITH_LLM = ("CHAT_LLM_CONFIG_PATH", "config/chat_llm_config.yaml") - RAG = ("BRAIN_CONFIG_PATH", "config/retrieval_config_workflow.yaml") - - @property - def env_var(self) -> str: - return self.value[0] - - @property - def default_path(self) -> str: - return self.value[1] - - -def get_config_file_path( - config_path_env: RetrievalConfigPathEnv, current_path: str | None = None -) -> str: - # Get the environment variable or fallback to the default path - _path = os.getenv(config_path_env.env_var, config_path_env.default_path) - - if not current_path: - return _path - - return os.path.join(current_path, _path) - - -def load_and_merge_retrieval_configuration( - config_file_path: str, sqlmodel: Model -) -> RetrievalConfig: - retrieval_config = RetrievalConfig.from_yaml(config_file_path) - field_mapping = { - "env_variable_name": "env_variable_name", - "endpoint_url": "llm_base_url", - } - - retrieval_config.llm_config.set_from_sqlmodel( - sqlmodel=sqlmodel, mapping=field_mapping - ) - - retrieval_config.llm_config.set_llm_model(sqlmodel.name) - - return retrieval_config - - -# TODO: rewrite -async def find_model_and_generate_metadata( - brain_model: str | None, - model_service: ModelService, -) -> Model: - model = await model_service.get_model(brain_model) - if model is None: - model = await model_service.get_default_model() - return model - - -def update_user_usage(usage: UserUsage, user_settings, cost: int = 100): - """Checks the user requests limit. - It checks the user requests limit and raises an exception if the user has reached the limit. - By default, the user has a limit of 100 requests per month. The limit can be increased by upgrading the plan. - - Args: - user (UserIdentity): User object - model (str): Model name for which the user is making the request - - Raises: - HTTPException: Raises a 429 error if the user has reached the limit. - """ - - date = time.strftime("%Y%m%d") - - monthly_chat_credit = user_settings.get("monthly_chat_credit", 100) - montly_usage = usage.get_user_monthly_usage(date) - - if int(montly_usage + cost) > int(monthly_chat_credit): - raise HTTPException( - status_code=429, # pyright: ignore reportPrivateUsage=none - detail=f"You have reached your monthly chat limit of {monthly_chat_credit} requests per months. Please upgrade your plan to increase your monthly chat limit.", - ) - else: - usage.handle_increment_user_request_count(date, cost) - pass - - -async def check_and_update_user_usage( - user: UserIdentity, model_name: str, model_service: ModelService -): - """Check user limits and raises if user reached his limits: - 1. Raise if one of the conditions : - - User doesn't have access to brains - - Model of brain is not is user_settings.models - - Latest sum_30d(user_daily_user) < user_settings.max_monthly_usage - - Check sum(user_settings.daily_user_count)+ model_price < user_settings.monthly_chat_credits - 2. Updates user usage - """ - # TODO(@aminediro) : THIS is bug prone, should retrieve it from DB here - user_usage = UserUsage(id=user.id, email=user.email) - user_settings = user_usage.get_user_settings() - - # Get the model to use - model = await model_service.get_model(model_name) - logger.info(f"Model 🔥: {model}") - if model is None: - model = await model_service.get_default_model() - logger.info(f"Model 🔥: {model}") - - # Raises HTTP if user usage exceeds limits - update_user_usage(user_usage, user_settings, model.price) # noqa: F821 - return model diff --git a/backend/api/quivr_api/modules/chat/controller/chat_routes.py b/backend/api/quivr_api/modules/chat/controller/chat_routes.py deleted file mode 100644 index f89e792c8..000000000 --- a/backend/api/quivr_api/modules/chat/controller/chat_routes.py +++ /dev/null @@ -1,385 +0,0 @@ -import os -from typing import Annotated, List, Optional -from uuid import UUID - -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request -from fastapi.responses import StreamingResponse -from quivr_core.config import RetrievalConfig - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.brain.entity.brain_entity import BrainEntity, RoleEnum -from quivr_api.modules.brain.service.brain_authorization_service import ( - validate_brain_authorization, -) -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.chat.controller.chat.utils import ( - RetrievalConfigPathEnv, - check_and_update_user_usage, - get_config_file_path, - load_and_merge_retrieval_configuration, -) -from quivr_api.modules.chat.dto.chats import ChatItem, ChatQuestion -from quivr_api.modules.chat.dto.inputs import ( - ChatMessageProperties, - ChatUpdatableProperties, - CreateChatProperties, - QuestionAndAnswer, -) -from quivr_api.modules.chat.entity.chat import Chat -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.prompt.service.prompt_service import PromptService -from quivr_api.modules.rag_service import RAGService -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.vector.service.vector_service import VectorService -from quivr_api.utils.telemetry import maybe_send_telemetry -from quivr_api.utils.uuid_generator import generate_uuid_from_string - -logger = get_logger(__name__) - -chat_router = APIRouter() -brain_service = BrainService() -KnowledgeServiceDep = Annotated[ - KnowledgeService, Depends(get_service(KnowledgeService)) -] -prompt_service = PromptService() - - -ChatServiceDep = Annotated[ChatService, Depends(get_service(ChatService))] -UserIdentityDep = Annotated[UserIdentity, Depends(get_current_user)] -ModelServiceDep = Annotated[ModelService, Depends(get_service(ModelService))] -VectorServiceDep = Annotated[VectorService, Depends(get_service(VectorService, False))] - - -def validate_authorization(user_id, brain_id): - if brain_id: - validate_brain_authorization( - brain_id=brain_id, - user_id=user_id, - required_roles=[RoleEnum.Viewer, RoleEnum.Editor, RoleEnum.Owner], - ) - - -@chat_router.get("/chat/healthz", tags=["Health"]) -async def healthz(): - return {"status": "ok"} - - -# get all chats -@chat_router.get("/chat", dependencies=[Depends(AuthBearer())], tags=["Chat"]) -async def get_chats(current_user: UserIdentityDep, chat_service: ChatServiceDep): - """ - Retrieve all chats for the current user. - - - `current_user`: The current authenticated user. - - Returns a list of all chats for the user. - - This endpoint retrieves all the chats associated with the current authenticated user. It returns a list of chat objects - containing the chat ID and chat name for each chat. - """ - chats = await chat_service.get_user_chats(current_user.id) - return {"chats": chats} - - -# delete one chat -@chat_router.delete( - "/chat/{chat_id}", dependencies=[Depends(AuthBearer())], tags=["Chat"] -) -async def delete_chat(chat_id: UUID, chat_service: ChatServiceDep): - """ - Delete a specific chat by chat ID. - """ - - chat_service.delete_chat_from_db(chat_id) - return {"message": f"{chat_id} has been deleted."} - - -# update existing chat metadata -@chat_router.put( - "/chat/{chat_id}/metadata", dependencies=[Depends(AuthBearer())], tags=["Chat"] -) -async def update_chat_metadata_handler( - chat_data: ChatUpdatableProperties, - chat_id: UUID, - current_user: UserIdentityDep, - chat_service: ChatServiceDep, -): - """ - Update chat attributes - """ - - chat = await chat_service.get_chat_by_id(chat_id) - if str(current_user.id) != str(chat.user_id): - raise HTTPException( - status_code=403, # pyright: ignore reportPrivateUsage=none - detail="You should be the owner of the chat to update it.", # pyright: ignore reportPrivateUsage=none - ) - return chat_service.update_chat(chat_id=chat_id, chat_data=chat_data) - - -# update existing message -@chat_router.put("/chat/{chat_id}/{message_id}", tags=["Chat"]) -async def update_chat_message( - chat_message_properties: ChatMessageProperties, - chat_id: UUID, - message_id: UUID, - current_user: UserIdentityDep, - chat_service: ChatServiceDep, -): - chat = await chat_service.get_chat_by_id( - chat_id # pyright: ignore reportPrivateUsage=none - ) - - if str(current_user.id) != str(chat.user_id): - raise HTTPException( - status_code=403, # pyright: ignore reportPrivateUsage=none - detail="You should be the owner of the chat to update it.", # pyright: ignore reportPrivateUsage=none - ) - return chat_service.update_chat_message( - chat_id=chat_id, - message_id=message_id, - chat_message_properties=chat_message_properties, - ) - - -# create new chat -@chat_router.post("/chat", dependencies=[Depends(AuthBearer())], tags=["Chat"]) -async def create_chat_handler( - chat_data: CreateChatProperties, - current_user: UserIdentityDep, - chat_service: ChatServiceDep, -): - """ - Create a new chat with initial chat messages. - """ - - return await chat_service.create_chat( - user_id=current_user.id, new_chat_data=chat_data - ) - - -# add new question to chat -@chat_router.post( - "/chat/{chat_id}/question", - dependencies=[ - Depends( - AuthBearer(), - ), - ], - tags=["Chat"], -) -async def create_question_handler( - request: Request, - chat_question: ChatQuestion, - chat_id: UUID, - current_user: UserIdentityDep, - chat_service: ChatServiceDep, - knowledge_service: KnowledgeServiceDep, - model_service: ModelServiceDep, - vector_service: VectorServiceDep, - brain_id: Annotated[UUID | None, Query()] = None, -): - models = await model_service.get_models() - - model_to_use = None - # Check if the brain_id is a model name hashed to a uuid and then returns the model name - # if chat_question.brain_id in [generate_uuid_from_string(model.name) for model in models]: - # mode - for model in models: - if brain_id == generate_uuid_from_string(model.name): - model_to_use = model - _brain = {"brain_id": brain_id, "name": model.name} - brain = BrainEntity(**_brain) - break - - try: - if not model_to_use: - brain = brain_service.get_brain_details(brain_id, current_user.id) # type: ignore - assert brain - model = await check_and_update_user_usage( - current_user, str(brain.model), model_service - ) # type: ignore - assert model is not None # type: ignore - assert brain is not None # type: ignore - - brain.model = model.name - validate_authorization(user_id=current_user.id, brain_id=brain_id) - service = RAGService( - current_user=current_user, - chat_id=chat_id, - brain=brain, - model_service=model_service, - brain_service=brain_service, - prompt_service=prompt_service, - chat_service=chat_service, - knowledge_service=knowledge_service, - vector_service=vector_service, - ) - else: - await check_and_update_user_usage( - current_user, model_to_use.name, model_service - ) # type: ignore - if not os.getenv("CHAT_LLM_CONFIG_PATH"): - raise ValueError("CHAT_LLM_CONFIG_PATH not set") - current_path = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(current_path, os.getenv("CHAT_LLM_CONFIG_PATH")) # type: ignore - retrieval_config = RetrievalConfig.from_yaml(file_path) - service = RAGService( - current_user=current_user, - chat_id=chat_id, - brain=brain, - retrieval_config=retrieval_config, - model_service=model_service, - chat_service=chat_service, - ) # type: ignore - assert service is not None # type: ignore - maybe_send_telemetry("question_asked", {"streaming": True}, request) - chat_answer = await service.generate_answer(chat_question.question) - - return chat_answer - - except AssertionError: - raise HTTPException( - status_code=422, - detail="inprocessable entity", - ) - except HTTPException as e: - raise e - - -# stream new question response from chat -@chat_router.post( - "/chat/{chat_id}/question/stream", - dependencies=[ - Depends( - AuthBearer(), - ), - ], - tags=["Chat"], -) -async def create_stream_question_handler( - request: Request, - chat_question: ChatQuestion, - chat_id: UUID, - chat_service: ChatServiceDep, - current_user: UserIdentityDep, - knowledge_service: KnowledgeServiceDep, - model_service: ModelServiceDep, - vector_service: VectorServiceDep, - background_tasks: BackgroundTasks, - brain_id: Annotated[UUID | None, Query()] = None, -) -> StreamingResponse: - logger.info( - f"Creating question for chat {chat_id} with brain {brain_id} of type {type(brain_id)}" - ) - - models = await model_service.get_models() - # Check if the brain_id is a model name hashed to a uuid and then returns the model name - # if chat_question.brain_id in [generate_uuid_from_string(model.name) for model in models]: - # mode - model_to_use = None - for model in models: - if brain_id == generate_uuid_from_string(model.name): - model_to_use = model - _brain = {"name": model.name} - brain = BrainEntity(**_brain) - break - try: - if model_to_use is None: - assert brain_id - brain = brain_service.get_brain_details(brain_id, current_user.id) - assert brain is not None - model = await check_and_update_user_usage( - current_user, str(brain.model), model_service - ) - assert model is not None - brain.model = model.name - validate_authorization(user_id=current_user.id, brain_id=brain_id) - current_path = os.path.dirname(os.path.abspath(__file__)) - file_path = get_config_file_path( - RetrievalConfigPathEnv.RAG, current_path=current_path - ) - retrieval_config = load_and_merge_retrieval_configuration( - config_file_path=file_path, sqlmodel=model - ) - service = RAGService( - current_user=current_user, - chat_id=chat_id, - brain=brain, - retrieval_config=retrieval_config, - model_service=model_service, - brain_service=brain_service, - prompt_service=prompt_service, - chat_service=chat_service, - knowledge_service=knowledge_service, - vector_service=vector_service, - ) - else: - await check_and_update_user_usage( - current_user, model_to_use.name, model_service - ) # type: ignore - current_path = os.path.dirname(os.path.abspath(__file__)) - file_path = get_config_file_path( - RetrievalConfigPathEnv.CHAT_WITH_LLM, current_path=current_path - ) - retrieval_config = load_and_merge_retrieval_configuration( - config_file_path=file_path, sqlmodel=model_to_use - ) - service = RAGService( - current_user=current_user, - chat_id=chat_id, - brain=brain, - retrieval_config=retrieval_config, - model_service=model_service, - chat_service=chat_service, - ) # type: ignore - - background_tasks.add_task( - maybe_send_telemetry, "question_asked", {"streaming": True}, request - ) - - return StreamingResponse( - service.generate_answer_stream(chat_question.question), - media_type="text/event-stream", - ) - - except AssertionError: - logger.error(f"assertion error request: {request}") - raise HTTPException( - status_code=422, - detail="inprocessable entity", - ) - except HTTPException as e: - raise e - - -# get chat history -@chat_router.get( - "/chat/{chat_id}/history", dependencies=[Depends(AuthBearer())], tags=["Chat"] -) -async def get_chat_history_handler( - chat_id: UUID, - chat_service: ChatServiceDep, -) -> List[ChatItem]: - return await chat_service.get_chat_history_with_notifications(chat_id) - - -@chat_router.post( - "/chat/{chat_id}/question/answer", - dependencies=[Depends(AuthBearer())], - tags=["Chat"], -) -async def add_question_and_answer_handler( - chat_id: UUID, - question_and_answer: QuestionAndAnswer, - chat_service: ChatServiceDep, -) -> Optional[Chat]: - """ - Add a new question and anwser to the chat. - """ - history = await chat_service.add_question_and_answer(chat_id, question_and_answer) - # TODO(@aminediro) : Do we need to return the chat ?? - return history.chat diff --git a/backend/api/quivr_api/modules/chat/controller/config/chat_llm_config.yaml b/backend/api/quivr_api/modules/chat/controller/config/chat_llm_config.yaml deleted file mode 100644 index bad270885..000000000 --- a/backend/api/quivr_api/modules/chat/controller/config/chat_llm_config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -workflow_config: - name: "Chat LLM" - nodes: - - name: "START" - edges: ["filter_history"] - - - name: "filter_history" - edges: ["generate_chat_llm"] - - - name: "generate_chat_llm" # the name of the last node, from which we want to stream the answer to the user, should always start with "generate" - edges: ["END"] -# Maximum number of previous conversation iterations -# to include in the context of the answer -max_history: 10 - -#prompt: "my prompt" - -llm_config: - max_input_tokens: 2000 - - # Maximum number of tokens to pass to the LLM - # as a context to generate the answer - max_output_tokens: 2000 - - temperature: 0.7 - streaming: true diff --git a/backend/api/quivr_api/modules/chat/controller/config/retrieval_config_workflow.yaml b/backend/api/quivr_api/modules/chat/controller/config/retrieval_config_workflow.yaml deleted file mode 100644 index b444f64d2..000000000 --- a/backend/api/quivr_api/modules/chat/controller/config/retrieval_config_workflow.yaml +++ /dev/null @@ -1,43 +0,0 @@ -workflow_config: - name: "standard RAG" - nodes: - - name: "START" - edges: ["filter_history"] - - - name: "filter_history" - edges: ["rewrite"] - - - name: "rewrite" - edges: ["retrieve"] - - - name: "retrieve" - edges: ["generate_rag"] - - - name: "generate_rag" # the name of the last node, from which we want to stream the answer to the user, should always start with "generate" - edges: ["END"] -# Maximum number of previous conversation iterations -# to include in the context of the answer -max_history: 10 - -prompt: "my prompt" - -max_files: 20 -reranker_config: - # The reranker supplier to use - supplier: "cohere" - - # The model to use for the reranker for the given supplier - model: "rerank-multilingual-v3.0" - - # Number of chunks returned by the reranker - top_n: 5 -llm_config: - - max_input_tokens: 2000 - - # Maximum number of tokens to pass to the LLM - # as a context to generate the answer - max_output_tokens: 2000 - - temperature: 0.7 - streaming: true diff --git a/backend/api/quivr_api/modules/chat/dto/__init__.py b/backend/api/quivr_api/modules/chat/dto/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/dto/chats.py b/backend/api/quivr_api/modules/chat/dto/chats.py deleted file mode 100644 index a04a6dcc7..000000000 --- a/backend/api/quivr_api/modules/chat/dto/chats.py +++ /dev/null @@ -1,48 +0,0 @@ -from enum import Enum -from typing import List, Optional, Tuple, Union -from uuid import UUID - -from pydantic import BaseModel - -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.notification.entity.notification import Notification - - -class ChatMessage(BaseModel): - model: str - question: str - # A list of tuples where each tuple is (speaker, text) - history: List[Tuple[str, str]] - temperature: float = 0.0 - max_tokens: int = 256 - chat_id: Optional[UUID] = None - chat_name: Optional[str] = None - - -class ChatQuestion(BaseModel): - question: str - model: Optional[str] = None - temperature: Optional[float] = None - max_tokens: Optional[int] = None - brain_id: Optional[UUID] = None - prompt_id: Optional[UUID] = None - - -class Sources(BaseModel): - name: str - source_url: str - type: str - original_file_name: str - citation: str - integration: Optional[str] = None - integration_link: Optional[str] = None - - -class ChatItemType(Enum): - MESSAGE = "MESSAGE" - NOTIFICATION = "NOTIFICATION" - - -class ChatItem(BaseModel): - item_type: ChatItemType - body: Union[GetChatHistoryOutput, Notification] diff --git a/backend/api/quivr_api/modules/chat/dto/inputs.py b/backend/api/quivr_api/modules/chat/dto/inputs.py deleted file mode 100644 index d3acab722..000000000 --- a/backend/api/quivr_api/modules/chat/dto/inputs.py +++ /dev/null @@ -1,46 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel - - -class CreateChatHistory(BaseModel): - chat_id: UUID - user_message: str - assistant: str - prompt_id: Optional[UUID] = None - brain_id: Optional[UUID] = None - metadata: Optional[dict] = {} - - -class QuestionAndAnswer(BaseModel): - question: str - answer: str - - -@dataclass -class CreateChatProperties: - name: str - - def __init__(self, name: str): - self.name = name - - -@dataclass -class ChatUpdatableProperties: - chat_name: Optional[str] = None - - def __init__(self, chat_name: Optional[str]): - self.chat_name = chat_name - - -class ChatMessageProperties(BaseModel, extra="ignore"): - thumbs: Optional[bool] - - def dict(self, *args, **kwargs): - chat_dict = super().dict(*args, **kwargs) - if chat_dict.get("thumbs"): - # Set thumbs to boolean value or None if not present - chat_dict["thumbs"] = bool(chat_dict["thumbs"]) - return chat_dict diff --git a/backend/api/quivr_api/modules/chat/dto/outputs.py b/backend/api/quivr_api/modules/chat/dto/outputs.py deleted file mode 100644 index 86ee1fd6a..000000000 --- a/backend/api/quivr_api/modules/chat/dto/outputs.py +++ /dev/null @@ -1,54 +0,0 @@ -from datetime import datetime -from typing import List, Optional -from uuid import UUID - -from pydantic import BaseModel - - -class GetChatHistoryOutput(BaseModel): - chat_id: UUID - message_id: UUID - user_message: str - message_time: datetime - assistant: str | None = None - prompt_title: str | None = None - brain_name: str | None = None - brain_id: UUID | None = None # string because UUID is not JSON serializable - metadata: Optional[dict] | None = None - thumbs: Optional[bool] | None = None - - def dict(self, *args, **kwargs): - chat_history = super().dict(*args, **kwargs) - chat_history["chat_id"] = str(chat_history.get("chat_id")) - chat_history["message_id"] = str(chat_history.get("message_id")) - - return chat_history - - -class FunctionCall(BaseModel): - arguments: str - name: str - - -class ChatCompletionMessageToolCall(BaseModel): - id: str - function: FunctionCall - type: str = "function" - - -class CompletionMessage(BaseModel): - # = "assistant" | "user" | "system" | "tool" - role: str - content: str | None = None - tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None - - -class CompletionResponse(BaseModel): - finish_reason: str - message: CompletionMessage - - -class BrainCompletionOutput(BaseModel): - messages: List[CompletionMessage] - question: str - response: CompletionResponse diff --git a/backend/api/quivr_api/modules/chat/entity/__init__.py b/backend/api/quivr_api/modules/chat/entity/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/entity/chat.py b/backend/api/quivr_api/modules/chat/entity/chat.py deleted file mode 100644 index 965b38da8..000000000 --- a/backend/api/quivr_api/modules/chat/entity/chat.py +++ /dev/null @@ -1,81 +0,0 @@ -from datetime import datetime -from typing import List -from uuid import UUID - -from sqlalchemy.ext.asyncio import AsyncAttrs -from sqlmodel import JSON, TIMESTAMP, Column, Field, Relationship, SQLModel, text -from sqlmodel import UUID as PGUUID - -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.user.entity.user_identity import User - - -class Chat(SQLModel, table=True): - __tablename__ = "chats" # type: ignore - chat_id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - chat_name: str | None - creation_time: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - user_id: UUID | None = Field(default=None, foreign_key="users.id") - user: User | None = Relationship(back_populates="chats") # type: ignore - chat_history: List["ChatHistory"] | None = Relationship(back_populates="chat") # type: ignore - - -class ChatHistory(AsyncAttrs, SQLModel, table=True): - __tablename__ = "chat_history" # type: ignore # type : ignore - - message_id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - chat_id: UUID | None = Field( - default=None, - foreign_key="chats.chat_id", - primary_key=True, - nullable=False, # Added nullable constraint - ) - chat: Chat | None = Relationship( - back_populates="chat_history", sa_relationship_kwargs={"lazy": "select"} - ) # type: ignore - user_message: str | None = None - assistant: str | None = None - message_time: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - metadata_: dict | None = Field( - default=None, sa_column=Column("metadata", JSON, default=None) - ) - prompt_id: UUID | None = Field(default=None, foreign_key="prompts.id") - brain_id: UUID | None = Field( - default=None, - foreign_key="brains.brain_id", - ) - - thumbs: bool | None = None - brain: Brain | None = Relationship( - back_populates="brain_chat_history", sa_relationship_kwargs={"lazy": "select"} - ) # type: ignore - - class Config: - # Note: Pydantic can't generate schema for arbitrary types - arbitrary_types_allowed = True diff --git a/backend/api/quivr_api/modules/chat/repository/__init__.py b/backend/api/quivr_api/modules/chat/repository/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/repository/chats.py b/backend/api/quivr_api/modules/chat/repository/chats.py deleted file mode 100644 index ed1feb0c6..000000000 --- a/backend/api/quivr_api/modules/chat/repository/chats.py +++ /dev/null @@ -1,123 +0,0 @@ -from typing import Sequence -from uuid import UUID - -from sqlalchemy import exc -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.chat.dto.inputs import ChatMessageProperties, QuestionAndAnswer -from quivr_api.modules.chat.entity.chat import Chat, ChatHistory -from quivr_api.modules.dependencies import BaseRepository, get_supabase_client - - -class ChatRepository(BaseRepository): - def __init__(self, session: AsyncSession): - super().__init__(session) - # TODO: for now use it instead of session - self.db = get_supabase_client() - - async def get_user_chats(self, user_id: UUID) -> Sequence[Chat]: - query = select(Chat).where(Chat.user_id == user_id) - response = await self.session.exec(query) - return response.all() - - async def create_chat(self, new_chat: Chat) -> Chat: - try: - self.session.add(new_chat) - await self.session.commit() - except exc.IntegrityError: - await self.session.rollback() - # TODO(@aminediro): Custom exceptions - raise Exception() - - await self.session.refresh(new_chat) - return new_chat - - async def get_chat_by_id(self, chat_id: UUID): - query = select(Chat).where(Chat.chat_id == chat_id) - response = await self.session.exec(query) - return response.one() - - async def get_chat_history(self, chat_id: UUID) -> Sequence[ChatHistory]: - query = ( - select(ChatHistory) - .where(ChatHistory.chat_id == chat_id) - # TODO: type hints of sqlmodel arent stable for order_by - .order_by(ChatHistory.message_time) # type: ignore - ) - response = await self.session.exec(query) - return response.all() - - async def add_question_and_answer( - self, chat_id: UUID, question_and_answer: QuestionAndAnswer - ) -> ChatHistory: - chat = ChatHistory( - chat_id=chat_id, - user_message=question_and_answer.question, - assistant=question_and_answer.answer, - ) - try: - self.session.add(chat) - await self.session.commit() - except exc.IntegrityError: - await self.session.rollback() - # TODO(@aminediro) : for now, build an exception system - raise Exception("can't create chat_history ") - await self.session.refresh(chat) - return chat - - def update_chat_history(self, chat_history): - response = ( - self.db.table("chat_history") - .insert( - { - "chat_id": str(chat_history.chat_id), - "user_message": chat_history.user_message, - "assistant": chat_history.assistant, - "prompt_id": ( - str(chat_history.prompt_id) if chat_history.prompt_id else None - ), - "brain_id": ( - str(chat_history.brain_id) if chat_history.brain_id else None - ), - "metadata": chat_history.metadata if chat_history.metadata else {}, - } - ) - .execute() - ) - return response - - def update_chat(self, chat_id, updates): - response = ( - self.db.table("chats").update(updates).match({"chat_id": chat_id}).execute() - ) - - return response - - def update_message_by_id(self, message_id, updates): - response = ( - self.db.table("chat_history") - .update(updates) - .match({"message_id": message_id}) - .execute() - ) - - return response - - def delete_chat(self, chat_id): - self.db.table("chats").delete().match({"chat_id": chat_id}).execute() - - def delete_chat_history(self, chat_id): - self.db.table("chat_history").delete().match({"chat_id": chat_id}).execute() - - def update_chat_message( - self, chat_id, message_id, chat_message_properties: ChatMessageProperties - ): - response = ( - self.db.table("chat_history") - .update(chat_message_properties.model_dump()) - .match({"message_id": message_id, "chat_id": chat_id}) - .execute() - ) - - return response diff --git a/backend/api/quivr_api/modules/chat/repository/chats_interface.py b/backend/api/quivr_api/modules/chat/repository/chats_interface.py deleted file mode 100644 index b0f63ea93..000000000 --- a/backend/api/quivr_api/modules/chat/repository/chats_interface.py +++ /dev/null @@ -1,93 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Optional -from uuid import UUID - -from quivr_api.modules.chat.dto.inputs import ( - ChatMessageProperties, - CreateChatHistory, - QuestionAndAnswer, -) -from quivr_api.modules.chat.entity.chat import Chat - - -class ChatsInterface(ABC): - @abstractmethod - def create_chat(self, new_chat): - """ - Insert a chat entry in "chats" db - """ - pass - - @abstractmethod - def get_chat_by_id(self, chat_id: str): - """ - Get chat details by chat_id - """ - pass - - @abstractmethod - def add_question_and_answer( - self, chat_id: UUID, question_and_answer: QuestionAndAnswer - ) -> Optional[Chat]: - """ - Add a question and answer to the chat history - """ - pass - - @abstractmethod - def get_chat_history(self, chat_id: str): - """ - Get chat history by chat_id - """ - pass - - @abstractmethod - def get_user_chats(self, user_id: str): - """ - Get all chats for a user - """ - pass - - @abstractmethod - def update_chat_history(self, chat_history: CreateChatHistory): - """ - Update chat history - """ - pass - - @abstractmethod - def update_chat(self, chat_id, updates): - """ - Update chat details - """ - pass - - @abstractmethod - def update_message_by_id(self, message_id, updates): - """ - Update message details - """ - pass - - @abstractmethod - def delete_chat(self, chat_id): - """ - Delete chat - """ - pass - - @abstractmethod - def delete_chat_history(self, chat_id): - """ - Delete chat history - """ - pass - - @abstractmethod - def update_chat_message( - self, chat_id, message_id, chat_message_properties: ChatMessageProperties - ): - """ - Update chat message - """ - pass diff --git a/backend/api/quivr_api/modules/chat/service/__init__.py b/backend/api/quivr_api/modules/chat/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/chat/service/chat_service.py b/backend/api/quivr_api/modules/chat/service/chat_service.py deleted file mode 100644 index 102c46ae2..000000000 --- a/backend/api/quivr_api/modules/chat/service/chat_service.py +++ /dev/null @@ -1,213 +0,0 @@ -import random -from typing import List -from uuid import UUID - -from fastapi import HTTPException - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.chat.dto.chats import ChatItem -from quivr_api.modules.chat.dto.inputs import ( - ChatMessageProperties, - ChatUpdatableProperties, - CreateChatHistory, - CreateChatProperties, - QuestionAndAnswer, -) -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.chat.entity.chat import Chat, ChatHistory -from quivr_api.modules.chat.repository.chats import ChatRepository -from quivr_api.modules.chat.service.utils import merge_chat_history_and_notifications -from quivr_api.modules.dependencies import BaseService -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.prompt.entity.prompt import Prompt -from quivr_api.modules.prompt.service.prompt_service import PromptService - -logger = get_logger(__name__) - -prompt_service = PromptService() -brain_service = BrainService() -notification_service = NotificationService() - - -class ChatService(BaseService[ChatRepository]): - repository_cls = ChatRepository - - def __init__(self, repository: ChatRepository): - self.repository = repository - - async def create_chat( - self, user_id: UUID, new_chat_data: CreateChatProperties - ) -> Chat: - # Chat is created upon the user's first question asked - logger.info(f"New chat entry in chats table for user {user_id}") - - inserted_chat = await self.repository.create_chat( - Chat(chat_name=new_chat_data.name, user_id=user_id) - ) - logger.info(f"Insert response {inserted_chat}") - - return inserted_chat - - def get_follow_up_question( - self, brain_id: UUID | None = None, question: str | None = None - ) -> list[str]: - follow_up = [ - "Summarize the conversation", - "Explain in more detail", - "Explain like I'm 5", - "Provide a list", - "Give examples", - "Use simpler language", - "Elaborate on a specific point", - "Provide pros and cons", - "Break down into steps", - "Illustrate with an image or diagram", - ] - # Return 3 random follow up questions amongs the list - random3 = random.sample(follow_up, 3) - return random3 - - async def add_question_and_answer( - self, chat_id: UUID, question_and_answer: QuestionAndAnswer - ) -> ChatHistory: - return await self.repository.add_question_and_answer( - chat_id, question_and_answer - ) - - async def get_chat_by_id(self, chat_id: UUID) -> Chat: - chat = await self.repository.get_chat_by_id(chat_id) - return chat - - async def get_chat_history(self, chat_id: UUID) -> List[GetChatHistoryOutput]: - history = await self.repository.get_chat_history(chat_id) - enriched_history: List[GetChatHistoryOutput] = [] - if len(history) == 0: - return enriched_history - for message in history: - brain: Brain | None = ( - await message.awaitable_attrs.brain if message.brain_id else None - ) - prompt: Prompt | None = None - if brain: - prompt = ( - await brain.awaitable_attrs.prompt if message.prompt_id else None - ) - enriched_history.append( - # TODO : WHY bother with having ids here ?? - GetChatHistoryOutput( - chat_id=(message.chat_id), - message_id=message.message_id, - user_message=message.user_message, - assistant=message.assistant, - message_time=message.message_time, - brain_name=brain.name if brain else None, - brain_id=brain.brain_id if brain else None, - prompt_title=(prompt.title if prompt else None), - metadata=message.metadata_, - thumbs=message.thumbs, - ) - ) - return enriched_history - - async def get_chat_history_with_notifications( - self, - chat_id: UUID, - ) -> List[ChatItem]: - chat_history = await self.get_chat_history(chat_id) - chat_notifications = [] - return merge_chat_history_and_notifications(chat_history, chat_notifications) - - async def get_user_chats(self, user_id: UUID) -> List[Chat]: - return list(await self.repository.get_user_chats(user_id)) - - def update_chat_history(self, chat_history: CreateChatHistory) -> ChatHistory: - response: List[ChatHistory] = ( - self.repository.update_chat_history(chat_history) - ).data - if len(response) == 0: - raise HTTPException( - status_code=500, - detail="An exception occurred while updating chat history.", - ) - return ChatHistory(**response[0]) # pyright: ignore reportPrivateUsage=none - - def update_chat(self, chat_id, chat_data: ChatUpdatableProperties) -> Chat: - if not chat_id: - logger.error("No chat_id provided") - return # pyright: ignore reportPrivateUsage=none - - updates = {} - - if chat_data.chat_name is not None: - updates["chat_name"] = chat_data.chat_name - - updated_chat = None - - if updates: - updated_chat = (self.repository.update_chat(chat_id, updates)).data[0] - logger.info(f"Chat {chat_id} updated") - else: - logger.info(f"No updates to apply for chat {chat_id}") - return updated_chat # pyright: ignore reportPrivateUsage=none - - def update_message_by_id( - self, - message_id: str, - user_message: str = None, # pyright: ignore reportPrivateUsage=none - assistant: str = None, # pyright: ignore reportPrivateUsage=none - metadata: dict = None, # pyright: ignore reportPrivateUsage=none - ) -> ChatHistory: - if not message_id: - logger.error("No message_id provided") - return # pyright: ignore reportPrivateUsage=none - - updates = {} - - if user_message is not None: - updates["user_message"] = user_message - - if assistant is not None: - updates["assistant"] = assistant - - if metadata is not None: - updates["metadata"] = metadata - - updated_message = None - - if updates: - updated_message = ( - self.repository.update_message_by_id(message_id, updates) - ).data[ # type: ignore - 0 - ] - logger.info(f"Message {message_id} updated") - else: - logger.info(f"No updates to apply for message {message_id}") - return ChatHistory(**updated_message) # pyright: ignore reportPrivateUsage=none - - def delete_chat_from_db(self, chat_id): - try: - self.repository.delete_chat_history(chat_id) - except Exception as e: - print(e) - pass - try: - self.repository.delete_chat(chat_id) - except Exception as e: - print(e) - pass - - def update_chat_message( - self, chat_id, message_id, chat_message_properties: ChatMessageProperties - ): - try: - return self.repository.update_chat_message( - chat_id, message_id, chat_message_properties - ).data - except Exception as e: - print(e) - pass diff --git a/backend/api/quivr_api/modules/chat/service/utils.py b/backend/api/quivr_api/modules/chat/service/utils.py deleted file mode 100644 index cc64dfb39..000000000 --- a/backend/api/quivr_api/modules/chat/service/utils.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import List - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.chat.dto.chats import ChatItem, ChatItemType -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.notification.entity.notification import Notification -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.prompt.service.prompt_service import PromptService - -logger = get_logger(__name__) - -prompt_service = PromptService() -brain_service = BrainService() -notification_service = NotificationService() - - -def merge_chat_history_and_notifications( - chat_history: List[GetChatHistoryOutput], notifications: List[Notification] -) -> List[ChatItem]: - chat_history_and_notifications = chat_history + notifications - - chat_history_and_notifications.sort( - key=lambda x: ( - x.message_time - if isinstance(x, GetChatHistoryOutput) and x.message_time - else x.datetime - ) - ) - - transformed_data = [] - for item in chat_history_and_notifications: - if isinstance(item, GetChatHistoryOutput): - item_type = ChatItemType.MESSAGE - body = item - else: - item_type = ChatItemType.NOTIFICATION - body = item - transformed_item = ChatItem(item_type=item_type, body=body) - transformed_data.append(transformed_item) - - return transformed_data diff --git a/backend/api/quivr_api/modules/chat/tests/test_chats.py b/backend/api/quivr_api/modules/chat/tests/test_chats.py deleted file mode 100644 index f844bf515..000000000 --- a/backend/api/quivr_api/modules/chat/tests/test_chats.py +++ /dev/null @@ -1,137 +0,0 @@ -from typing import List, Tuple -from uuid import uuid4 - -import pytest -import pytest_asyncio -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.chat.dto.inputs import QuestionAndAnswer -from quivr_api.modules.chat.entity.chat import Chat, ChatHistory -from quivr_api.modules.chat.repository.chats import ChatRepository -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.user.entity.user_identity import User -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - -pg_database_base_url = "postgres:postgres@localhost:54322/postgres" - -TestData = Tuple[Brain, User, List[Chat], List[ChatHistory]] - -N_SEED_CHATS = 3 -n_seed_chats_history = 3 - - -@pytest_asyncio.fixture(scope="function") -async def test_data( - session: AsyncSession, -) -> TestData: - # User data - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - # Brain data - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - # Chat data - chat_1 = Chat(chat_name="chat1", user=user_1) - chat_2 = Chat(chat_name="chat2", user=user_1) - - chat_history_1 = ChatHistory( - user_message="Hello", - assistant="Hello! How can I assist you today?", - chat=chat_1, - brain=brain_1, - ) - chat_history_2 = ChatHistory( - user_message="Hello", - assistant="Hello! How can I assist you today?", - chat=chat_1, - brain=brain_1, - ) - session.add(brain_1) - session.add(chat_1) - session.add(chat_2) - session.add(chat_history_1) - session.add(chat_history_2) - - await session.refresh(user_1) - await session.commit() - return brain_1, user_1, [chat_1, chat_2], [chat_history_1, chat_history_2] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_user_chats_empty(session): - repo = ChatRepository(session) - chats = await repo.get_user_chats(user_id=uuid4()) - assert len(chats) == 0 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_user_chats(session: AsyncSession, test_data: TestData): - _, local_user, chats, _ = test_data - repo = ChatRepository(session) - assert local_user.id is not None - query_chats = await repo.get_user_chats(local_user.id) - assert len(query_chats) == len(chats) + N_SEED_CHATS - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_chat_history_close(session: AsyncSession, test_data: TestData): - brain_1, _, chats, chat_history = test_data - assert chats[0].chat_id - assert len(chat_history) > 0 - assert chat_history[-1].message_time - assert chat_history[0].message_time - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_chat_history(session: AsyncSession, test_data: TestData): - brain_1, _, chats, chat_history = test_data - assert chats[0].chat_id - assert len(chat_history) > 0 - assert chat_history[-1].message_time - assert chat_history[0].message_time - - repo = ChatRepository(session) - query_chat_history = await repo.get_chat_history(chats[0].chat_id) - assert chat_history == query_chat_history - assert query_chat_history[-1].message_time - assert query_chat_history[0].message_time - assert query_chat_history[-1].message_time >= query_chat_history[0].message_time - - # TODO: Should be tested in test_brain_repository - # Checks that brain is correct - assert query_chat_history[-1].brain is not None - assert query_chat_history[-1].brain.brain_type == BrainType.integration - - -@pytest.mark.asyncio(loop_scope="session") -async def test_add_qa(session: AsyncSession, test_data: TestData): - _, _, [chat, *_], __ = test_data - assert chat.chat_id - qa = QuestionAndAnswer(question="question", answer="answer") - repo = ChatRepository(session) - resp_chat = await repo.add_question_and_answer(chat.chat_id, qa) - - assert resp_chat.chat_id == chat.chat_id - assert resp_chat.user_message == qa.question - assert resp_chat.assistant == qa.answer - - -# CHAT SERVICE - - -@pytest.mark.asyncio(loop_scope="session") -async def test_service_get_chat_history(session: AsyncSession, test_data: TestData): - brain, _, [chat, *_], __ = test_data - assert chat.chat_id - repo = ChatRepository(session) - service = ChatService(repo) - history = await service.get_chat_history(chat.chat_id) - - assert len(history) > 0 - assert all(h.chat_id == chat.chat_id for h in history) - assert history[0].brain_name == brain.name - assert history[0].brain_id == brain.brain_id diff --git a/backend/api/quivr_api/modules/conftest.py b/backend/api/quivr_api/modules/conftest.py deleted file mode 100644 index d9def549c..000000000 --- a/backend/api/quivr_api/modules/conftest.py +++ /dev/null @@ -1,80 +0,0 @@ -import os - -import pytest -import pytest_asyncio -import sqlalchemy -from sqlalchemy.ext.asyncio import create_async_engine -from sqlmodel import Session, create_engine -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.dependencies import get_supabase_client - -pg_database_base_url = "postgres:postgres@localhost:54322/postgres" - -sync_engine = create_engine( - "postgresql://" + pg_database_base_url, - echo=True if os.getenv("ORM_DEBUG") else False, - pool_pre_ping=True, - pool_size=10, - pool_recycle=0.1, -) - - -async_engine = create_async_engine( - "postgresql+asyncpg://" + pg_database_base_url, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, -) - - -@pytest.fixture(scope="function") -def sync_session(): - with sync_engine.connect() as conn: - trans = conn.begin() - nested = conn.begin_nested() - sync_session = Session( - conn, - expire_on_commit=False, - autoflush=False, - autocommit=False, - ) - - @sqlalchemy.event.listens_for(sync_session, "after_transaction_end") - def end_savepoint(session, transaction): - nonlocal nested - if not nested.is_active: - nested = conn.begin_nested() - - yield sync_session - trans.rollback() - sync_session.close() - - -@pytest_asyncio.fixture(scope="function") -async def session(): - async with async_engine.connect() as conn: - trans = await conn.begin() - nested = await conn.begin_nested() - async_session = AsyncSession( - conn, - expire_on_commit=False, - autoflush=False, - autocommit=False, - ) - - @sqlalchemy.event.listens_for( - async_session.sync_session, "after_transaction_end" - ) - def end_savepoint(session, transaction): - nonlocal nested - if not nested.is_active: - nested = conn.sync_connection.begin_nested() - - yield async_session - await trans.rollback() - await async_session.close() - - -@pytest.fixture(scope="session") -def supabase_client(): - return get_supabase_client() diff --git a/backend/api/quivr_api/modules/dependencies.py b/backend/api/quivr_api/modules/dependencies.py deleted file mode 100644 index e74bdee45..000000000 --- a/backend/api/quivr_api/modules/dependencies.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -from typing import AsyncGenerator, Callable, Generator, Generic, Optional, Type, TypeVar -from urllib.parse import urlparse - -from fastapi import Depends -from langchain.embeddings.base import Embeddings -from langchain_community.embeddings.ollama import OllamaEmbeddings - -# from langchain_community.vectorstores.supabase import SupabaseVectorStore -from langchain_openai import AzureOpenAIEmbeddings, OpenAIEmbeddings - -# from quivr_api.modules.vector.service.vector_service import VectorService -# from quivr_api.modules.vectorstore.supabase import CustomSupabaseVectorStore -from sqlalchemy import Engine, create_engine -from sqlalchemy.ext.asyncio import create_async_engine -from sqlmodel import Session, text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.logger import get_logger -from quivr_api.models.databases.supabase.supabase import SupabaseDB -from quivr_api.models.settings import BrainSettings -from supabase.client import AsyncClient, Client, create_async_client, create_client - -# Global variables to store the Supabase client and database instances -_supabase_client: Optional[Client] = None -_supabase_async_client: Optional[AsyncClient] = None -_supabase_db: Optional[SupabaseDB] = None -_db_engine: Optional[Engine] = None -_embedding_service = None - -settings = BrainSettings() # type: ignore - -logger = get_logger("quivr_api") - - -class BaseRepository: - def __init__(self, session: AsyncSession | Session): - self.session = session - - -R = TypeVar("R", bound=BaseRepository) - - -class BaseService(Generic[R]): - # associated repository type - repository_cls: Type[R] - - def __init__(self, repository: R): - self.repository = repository - - @classmethod - def get_repository_cls(cls) -> Type[R]: - return cls.repository_cls # type: ignore - - -S = TypeVar("S", bound=BaseService) - -sync_engine = create_engine( - settings.pg_database_url, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, - # NOTE: pessimistic bound on - pool_pre_ping=True, - pool_size=10, # NOTE: no bouncer for now, if 6 process workers => 6 - pool_recycle=1800, -) -async_engine = create_async_engine( - settings.pg_database_async_url, - connect_args={"server_settings": {"application_name": "quivr-api-async"}}, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, - pool_pre_ping=True, - pool_size=5, # NOTE: no bouncer for now, if 6 process workers => 6 - pool_recycle=1800, - isolation_level="AUTOCOMMIT", -) - - -def get_sync_session() -> Generator[Session, None, None]: - with Session(sync_engine, expire_on_commit=False, autoflush=False) as session: - try: - session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - yield session - session.commit() - except Exception as e: - session.rollback() - raise e - finally: - session.close() - - -async def get_async_session() -> AsyncGenerator[AsyncSession, None]: - async with AsyncSession( - async_engine, - ) as session: - try: - await session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - yield session - await session.commit() - except Exception as e: - await session.rollback() - raise e - finally: - await session.close() - - -def get_repository(repository_model: Type[R], asynchronous=True) -> Callable[..., R]: - def _get_repository(session: AsyncSession = Depends(get_async_session)) -> R: - return repository_model(session) - - def _get_sync_repository(session: Session = Depends(get_sync_session)) -> R: - return repository_model(session) - - if asynchronous: - return _get_repository - return _get_sync_repository - - -def get_embedding_client() -> Embeddings: - global _embedding_service - if settings.ollama_api_base_url: - embeddings = OllamaEmbeddings( - base_url=settings.ollama_api_base_url, - ) # pyright: ignore reportPrivateUsage=none - elif settings.azure_openai_embeddings_url: - # https://quivr-test.openai.azure.com/openai/deployments/embedding/embeddings?api-version=2023-05-15 - # parse the url to get the deployment name - deployment = settings.azure_openai_embeddings_url.split("/")[5] - netloc = "https://" + urlparse(settings.azure_openai_embeddings_url).netloc - api_version = settings.azure_openai_embeddings_url.split("=")[1] - logger.debug(f"Using Azure OpenAI embeddings: {deployment}") - logger.debug(f"Using Azure OpenAI embeddings: {netloc}") - logger.debug(f"Using Azure OpenAI embeddings: {api_version}") - embeddings = AzureOpenAIEmbeddings( - azure_deployment=deployment, - azure_endpoint=netloc, - api_version=api_version, - ) - else: - embeddings = OpenAIEmbeddings() # pyright: ignore reportPrivateUsage=none - return embeddings - - -def get_pg_database_engine(): - global _db_engine - if _db_engine is None: - logger.info("Creating Postgres DB engine") - _db_engine = create_engine(settings.pg_database_url, pool_pre_ping=True) - return _db_engine - - -def get_pg_database_async_engine(): - global _db_engine - if _db_engine is None: - logger.info("Creating Postgres DB engine") - _db_engine = create_engine(settings.pg_database_async_url, pool_pre_ping=True) - return _db_engine - - -async def get_supabase_async_client() -> AsyncClient: - global _supabase_async_client - if _supabase_async_client is None: - logger.info("Creating Supabase client") - _supabase_async_client = await create_async_client( - settings.supabase_url, settings.supabase_service_key - ) - return _supabase_async_client - - -def get_supabase_client() -> Client: - global _supabase_client - if _supabase_client is None: - logger.info("Creating Supabase client") - _supabase_client = create_client( - settings.supabase_url, settings.supabase_service_key - ) - return _supabase_client - - -def get_supabase_db() -> SupabaseDB: - global _supabase_db - if _supabase_db is None: - logger.info("Creating Supabase DB") - _supabase_db = SupabaseDB(get_supabase_client()) - return _supabase_db - - -def get_service(service: Type[S], asynchronous=True) -> Callable[..., S]: - def _get_service( - repository: BaseRepository = Depends( - get_repository(service.get_repository_cls(), asynchronous) # type: ignore - ), - ) -> S: - return service(repository) - - return _get_service diff --git a/backend/api/quivr_api/modules/knowledge/__init__.py b/backend/api/quivr_api/modules/knowledge/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/knowledge/controller/__init__.py b/backend/api/quivr_api/modules/knowledge/controller/__init__.py deleted file mode 100644 index 911883cdc..000000000 --- a/backend/api/quivr_api/modules/knowledge/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .knowledge_routes import knowledge_router diff --git a/backend/api/quivr_api/modules/knowledge/controller/knowledge_routes.py b/backend/api/quivr_api/modules/knowledge/controller/knowledge_routes.py deleted file mode 100644 index 68d01afb0..000000000 --- a/backend/api/quivr_api/modules/knowledge/controller/knowledge_routes.py +++ /dev/null @@ -1,267 +0,0 @@ -from http import HTTPStatus -from typing import Annotated, List, Optional -from uuid import UUID - -from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.brain.entity.brain_entity import RoleEnum -from quivr_api.modules.brain.service.brain_authorization_service import ( - has_brain_authorization, - validate_brain_authorization, -) -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.knowledge.dto.inputs import AddKnowledge -from quivr_api.modules.knowledge.entity.knowledge import Knowledge, KnowledgeUpdate -from quivr_api.modules.knowledge.service.knowledge_exceptions import ( - KnowledgeDeleteError, - KnowledgeForbiddenAccess, - KnowledgeNotFoundException, - UploadError, -) -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.upload.service.generate_file_signed_url import ( - generate_file_signed_url, -) -from quivr_api.modules.user.entity.user_identity import UserIdentity - -knowledge_router = APIRouter() -logger = get_logger(__name__) - -get_km_service = get_service(KnowledgeService) -KnowledgeServiceDep = Annotated[KnowledgeService, Depends(get_km_service)] - - -@knowledge_router.get( - "/knowledge", dependencies=[Depends(AuthBearer())], tags=["Knowledge"] -) -async def list_knowledge_in_brain_endpoint( - knowledge_service: KnowledgeServiceDep, - brain_id: UUID = Query(..., description="The ID of the brain"), - current_user: UserIdentity = Depends(get_current_user), -): - """ - Retrieve and list all the knowledge in a brain. - """ - - validate_brain_authorization(brain_id=brain_id, user_id=current_user.id) - - knowledges = await knowledge_service.get_all_knowledge_in_brain(brain_id) - - return {"knowledges": knowledges} - - -@knowledge_router.delete( - "/knowledge/{knowledge_id}", - dependencies=[ - Depends(AuthBearer()), - Depends(has_brain_authorization(RoleEnum.Owner)), - ], - tags=["Knowledge"], -) -async def delete_knowledge_brain( - knowledge_id: UUID, - knowledge_service: KnowledgeServiceDep, - current_user: UserIdentity = Depends(get_current_user), - brain_id: UUID = Query(..., description="The ID of the brain"), -): - """ - Delete a specific knowledge from a brain. - """ - - knowledge = await knowledge_service.get_knowledge(knowledge_id) - file_name = knowledge.file_name if knowledge.file_name else knowledge.url - await knowledge_service.remove_knowledge_brain(brain_id, knowledge_id) - - return { - "message": f"{file_name} of brain {brain_id} has been deleted by user {current_user.email}." - } - - -@knowledge_router.get( - "/knowledge/{knowledge_id}/signed_download_url", - dependencies=[Depends(AuthBearer())], - tags=["Knowledge"], -) -async def generate_signed_url_endpoint( - knowledge_id: UUID, - knowledge_service: KnowledgeServiceDep, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Generate a signed url to download the file from storage. - """ - - knowledge = await knowledge_service.get_knowledge(knowledge_id) - - if len(knowledge.brains) == 0: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail="knowledge not associated with brains yet.", - ) - - brain_id = knowledge.brains[0]["brain_id"] - - validate_brain_authorization(brain_id=brain_id, user_id=current_user.id) - - if knowledge.file_name is None: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail=f"Knowledge with id {knowledge_id} is not a file.", - ) - - file_path_in_storage = f"{brain_id}/{knowledge.file_name}" - file_signed_url = generate_file_signed_url(file_path_in_storage) - - return file_signed_url - - -@knowledge_router.post( - "/knowledge/", - tags=["Knowledge"], - response_model=Knowledge, -) -async def create_knowledge( - knowledge_data: str = File(...), - file: Optional[UploadFile] = None, - knowledge_service: KnowledgeService = Depends(get_km_service), - current_user: UserIdentity = Depends(get_current_user), -): - knowledge = AddKnowledge.model_validate_json(knowledge_data) - if not knowledge.file_name and not knowledge.url: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Either file_name or url must be provided", - ) - try: - km = await knowledge_service.create_knowledge( - knowledge_to_add=knowledge, upload_file=file, user_id=current_user.id - ) - km_dto = await km.to_dto() - return km_dto - except ValueError: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Unprocessable knowledge ", - ) - except FileExistsError: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Existing knowledge" - ) - except UploadError: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error occured uploading knowledge", - ) - except Exception: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@knowledge_router.get( - "/knowledge/children", - response_model=List[Knowledge] | None, - tags=["Knowledge"], -) -async def list_knowledge( - parent_id: UUID | None = None, - knowledge_service: KnowledgeService = Depends(get_km_service), - current_user: UserIdentity = Depends(get_current_user), -): - try: - # TODO: Returns one level of children - children = await knowledge_service.list_knowledge(parent_id, current_user.id) - return [await c.to_dto(get_children=False) for c in children] - except KnowledgeNotFoundException as e: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail=f"{e.message}" - ) - except KnowledgeForbiddenAccess as e: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=f"{e.message}" - ) - except Exception: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@knowledge_router.get( - "/knowledge/{knowledge_id}", - response_model=Knowledge, - tags=["Knowledge"], -) -async def get_knowledge( - knowledge_id: UUID, - knowledge_service: KnowledgeService = Depends(get_km_service), - current_user: UserIdentity = Depends(get_current_user), -): - try: - km = await knowledge_service.get_knowledge(knowledge_id) - if km.user_id != current_user.id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You do not have permission to access this knowledge.", - ) - return await km.to_dto() - except KnowledgeNotFoundException as e: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=f"{e.message}" - ) - except Exception: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@knowledge_router.patch( - "/knowledge/{knowledge_id}", - status_code=status.HTTP_202_ACCEPTED, - response_model=Knowledge, - tags=["Knowledge"], -) -async def update_knowledge( - knowledge_id: UUID, - payload: KnowledgeUpdate, - knowledge_service: KnowledgeService = Depends(get_km_service), - current_user: UserIdentity = Depends(get_current_user), -): - try: - km = await knowledge_service.get_knowledge(knowledge_id) - if km.user_id != current_user.id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You do not have permission to access this knowledge.", - ) - km = await knowledge_service.update_knowledge(km, payload) - return km - except KnowledgeNotFoundException as e: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=f"{e.message}" - ) - except Exception: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@knowledge_router.delete( - "/knowledge/{knowledge_id}", - status_code=status.HTTP_202_ACCEPTED, - tags=["Knowledge"], -) -async def delete_knowledge( - knowledge_id: UUID, - knowledge_service: KnowledgeService = Depends(get_km_service), - current_user: UserIdentity = Depends(get_current_user), -): - try: - km = await knowledge_service.get_knowledge(knowledge_id) - - if km.user_id != current_user.id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You do not have permission to remove this knowledge.", - ) - delete_response = await knowledge_service.remove_knowledge(km) - return delete_response - except KnowledgeNotFoundException as e: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=f"{e.message}" - ) - except KnowledgeDeleteError: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/backend/api/quivr_api/modules/knowledge/dto/__init__.py b/backend/api/quivr_api/modules/knowledge/dto/__init__.py deleted file mode 100644 index 4f3a4b9f7..000000000 --- a/backend/api/quivr_api/modules/knowledge/dto/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .inputs import CreateKnowledgeProperties -from .outputs import DeleteKnowledgeResponse diff --git a/backend/api/quivr_api/modules/knowledge/dto/inputs.py b/backend/api/quivr_api/modules/knowledge/dto/inputs.py deleted file mode 100644 index 85a2438e9..000000000 --- a/backend/api/quivr_api/modules/knowledge/dto/inputs.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Dict, Optional -from uuid import UUID - -from pydantic import BaseModel -from quivr_core.models import KnowledgeStatus - - -class CreateKnowledgeProperties(BaseModel): - brain_id: UUID - file_name: Optional[str] = None - url: Optional[str] = None - extension: str = ".txt" - status: KnowledgeStatus = KnowledgeStatus.PROCESSING - source: str = "local" - source_link: Optional[str] = None - file_size: Optional[int] = None - file_sha1: Optional[str] = None - metadata: Optional[Dict[str, str]] = None - is_folder: bool = False - parent_id: Optional[UUID] = None - - -class AddKnowledge(BaseModel): - file_name: Optional[str] = None - url: Optional[str] = None - extension: str = ".txt" - source: str = "local" - source_link: Optional[str] = None - metadata: Optional[Dict[str, str]] = None - is_folder: bool = False - parent_id: Optional[UUID] = None diff --git a/backend/api/quivr_api/modules/knowledge/dto/outputs.py b/backend/api/quivr_api/modules/knowledge/dto/outputs.py deleted file mode 100644 index 20218dfce..000000000 --- a/backend/api/quivr_api/modules/knowledge/dto/outputs.py +++ /dev/null @@ -1,9 +0,0 @@ -from uuid import UUID - -from pydantic import BaseModel - - -class DeleteKnowledgeResponse(BaseModel): - file_name: str | None = None - status: str = "DELETED" - knowledge_id: UUID diff --git a/backend/api/quivr_api/modules/knowledge/entity/__init__.py b/backend/api/quivr_api/modules/knowledge/entity/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/knowledge/entity/knowledge.py b/backend/api/quivr_api/modules/knowledge/entity/knowledge.py deleted file mode 100644 index e08f3c0ab..000000000 --- a/backend/api/quivr_api/modules/knowledge/entity/knowledge.py +++ /dev/null @@ -1,152 +0,0 @@ -from datetime import datetime -from enum import Enum -from typing import Any, Dict, List, Optional -from uuid import UUID - -from pydantic import BaseModel -from quivr_core.models import KnowledgeStatus -from sqlalchemy import JSON, TIMESTAMP, Column, text -from sqlalchemy.ext.asyncio import AsyncAttrs -from sqlmodel import UUID as PGUUID -from sqlmodel import Field, Relationship, SQLModel - -from quivr_api.modules.knowledge.entity.knowledge_brain import KnowledgeBrain - - -class KnowledgeSource(str, Enum): - LOCAL = "local" - WEB = "web" - GDRIVE = "google drive" - DROPBOX = "dropbox" - SHAREPOINT = "sharepoint" - - -class Knowledge(BaseModel): - id: UUID - file_size: int = 0 - status: KnowledgeStatus - file_name: Optional[str] = None - url: Optional[str] = None - extension: str = ".txt" - is_folder: bool = False - updated_at: datetime - created_at: datetime - source: Optional[str] = None - source_link: Optional[str] = None - file_sha1: Optional[str] = None - metadata: Optional[Dict[str, str]] = None - user_id: Optional[UUID] = None - brains: List[Dict[str, Any]] - parent: Optional["Knowledge"] - children: Optional[list["Knowledge"]] - - -class KnowledgeUpdate(BaseModel): - file_name: Optional[str] = None - status: Optional[KnowledgeStatus] = None - url: Optional[str] = None - file_sha1: Optional[str] = None - extension: Optional[str] = None - parent_id: Optional[UUID] = None - source: Optional[str] = None - source_link: Optional[str] = None - metadata: Optional[Dict[str, str]] = None - - -class KnowledgeDB(AsyncAttrs, SQLModel, table=True): - __tablename__ = "knowledge" # type: ignore - - id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - file_name: str = Field(default="", max_length=255) - url: Optional[str] = Field(default=None, max_length=2048) - extension: str = Field(default=".txt", max_length=100) - status: str = Field(max_length=50) - source: str = Field(max_length=255) - source_link: Optional[str] = Field(max_length=2048) - file_size: Optional[int] = Field(gt=0) # FIXME: Should not be optional @chloedia - file_sha1: Optional[str] = Field( - max_length=40 - ) # FIXME: Should not be optional @chloedia - created_at: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - updated_at: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - onupdate=datetime.utcnow, - ), - ) - metadata_: Optional[Dict[str, str]] = Field( - default=None, sa_column=Column("metadata", JSON) - ) - is_folder: bool = Field(default=False) - user_id: UUID = Field(foreign_key="users.id", nullable=False) - brains: List["Brain"] = Relationship( # type: ignore # noqa: F821 - back_populates="knowledges", - link_model=KnowledgeBrain, - sa_relationship_kwargs={"lazy": "select"}, - ) - - parent_id: UUID | None = Field( - default=None, foreign_key="knowledge.id", ondelete="CASCADE" - ) - parent: Optional["KnowledgeDB"] = Relationship( - back_populates="children", - sa_relationship_kwargs={"remote_side": "KnowledgeDB.id"}, - ) - children: list["KnowledgeDB"] = Relationship( - back_populates="parent", - sa_relationship_kwargs={ - "cascade": "all, delete-orphan", - }, - ) - - # TODO: nested folder search - async def to_dto( - self, get_children: bool = True, get_parent: bool = True - ) -> Knowledge: - assert ( - self.updated_at - ), "knowledge should be inserted before transforming to dto" - assert ( - self.created_at - ), "knowledge should be inserted before transforming to dto" - brains = await self.awaitable_attrs.brains - children: list[KnowledgeDB] = ( - await self.awaitable_attrs.children if get_children else [] - ) - parent = await self.awaitable_attrs.parent if get_parent else None - parent = await parent.to_dto(get_children=False) if parent else None - - return Knowledge( - id=self.id, # type: ignore - file_name=self.file_name, - url=self.url, - extension=self.extension, - status=KnowledgeStatus(self.status), - source=self.source, - source_link=self.source_link, - is_folder=self.is_folder, - file_size=self.file_size or 0, - file_sha1=self.file_sha1, - updated_at=self.updated_at, - created_at=self.created_at, - metadata=self.metadata_, # type: ignore - brains=[b.model_dump() for b in brains], - parent=parent, - children=[await c.to_dto(get_children=False) for c in children], - user_id=self.user_id, - ) diff --git a/backend/api/quivr_api/modules/knowledge/entity/knowledge_brain.py b/backend/api/quivr_api/modules/knowledge/entity/knowledge_brain.py deleted file mode 100644 index 0f9b8e8ae..000000000 --- a/backend/api/quivr_api/modules/knowledge/entity/knowledge_brain.py +++ /dev/null @@ -1,32 +0,0 @@ -from datetime import datetime -from uuid import UUID - -from sqlalchemy import TIMESTAMP, Column, text -from sqlmodel import TIMESTAMP, Column, Field, SQLModel, text -from sqlmodel import UUID as PGUUID - - -class KnowledgeBrain(SQLModel, table=True): - __tablename__ = "knowledge_brain" # type: ignore - - id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - created_at: datetime | None = Field( - default=None, - sa_column=Column( - TIMESTAMP(timezone=False), - server_default=text("CURRENT_TIMESTAMP"), - ), - ) - brain_id: UUID = Field( - nullable=False, foreign_key="brains.brain_id", primary_key=True - ) - knowledge_id: UUID = Field( - nullable=False, foreign_key="knowledge.id", primary_key=True - ) diff --git a/backend/api/quivr_api/modules/knowledge/repository/__init__.py b/backend/api/quivr_api/modules/knowledge/repository/__init__.py deleted file mode 100644 index 80f13aa5f..000000000 --- a/backend/api/quivr_api/modules/knowledge/repository/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .knowledges import KnowledgeRepository diff --git a/backend/api/quivr_api/modules/knowledge/repository/knowledge_interface.py b/backend/api/quivr_api/modules/knowledge/repository/knowledge_interface.py deleted file mode 100644 index 65da8898a..000000000 --- a/backend/api/quivr_api/modules/knowledge/repository/knowledge_interface.py +++ /dev/null @@ -1,68 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List -from uuid import UUID - -from quivr_api.modules.knowledge.dto.inputs import ( - CreateKnowledgeProperties, - KnowledgeStatus, -) -from quivr_api.modules.knowledge.dto.outputs import DeleteKnowledgeResponse -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB - - -class KnowledgeInterface(ABC): - @abstractmethod - def insert_knowledge(self, knowledge: CreateKnowledgeProperties) -> KnowledgeDB: - """ - Add a knowledge - """ - pass - - @abstractmethod - def remove_knowledge_by_id( - # todo: update remove brain endpoints to first delete the knowledge - self, - knowledge_id: UUID, - ) -> DeleteKnowledgeResponse: - """ - Args: - knowledge_id (UUID): The id of the knowledge - - Returns: - str: Status message - """ - pass - - @abstractmethod - def get_knowledge_by_id(self, knowledge_id: UUID) -> KnowledgeDB: - """ - Get a knowledge by its id - Args: - brain_id (UUID): The id of the brain - """ - pass - - @abstractmethod - def get_all_knowledge_in_brain(self, brain_id: UUID) -> List[KnowledgeDB]: - """ - Get all the knowledge in a brain - Args: - brain_id (UUID): The id of the brain - """ - pass - - @abstractmethod - def remove_brain_all_knowledge(self, brain_id: UUID) -> None: - """ - Remove all knowledge in a brain - Args: - brain_id (UUID): The id of the brain - """ - pass - - @abstractmethod - def update_status_knowledge(self, knowledge_id: UUID, status: KnowledgeStatus): - """ - Update the status of a knowledge - """ - pass diff --git a/backend/api/quivr_api/modules/knowledge/repository/knowledges.py b/backend/api/quivr_api/modules/knowledge/repository/knowledges.py deleted file mode 100644 index 82da7c8e8..000000000 --- a/backend/api/quivr_api/modules/knowledge/repository/knowledges.py +++ /dev/null @@ -1,348 +0,0 @@ -from typing import Any, Sequence -from uuid import UUID - -from fastapi import HTTPException -from quivr_core.models import KnowledgeStatus -from sqlalchemy.exc import IntegrityError, NoResultFound -from sqlalchemy.orm import joinedload -from sqlmodel import select, text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.dependencies import BaseRepository, get_supabase_client -from quivr_api.modules.knowledge.dto.outputs import DeleteKnowledgeResponse -from quivr_api.modules.knowledge.entity.knowledge import ( - Knowledge, - KnowledgeDB, - KnowledgeUpdate, -) -from quivr_api.modules.knowledge.service.knowledge_exceptions import ( - KnowledgeNotFoundException, - KnowledgeUpdateError, -) - -logger = get_logger(__name__) - - -class KnowledgeRepository(BaseRepository): - def __init__(self, session: AsyncSession): - self.session = session - supabase_client = get_supabase_client() - self.db = supabase_client - - async def create_knowledge(self, knowledge: KnowledgeDB) -> KnowledgeDB: - try: - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - except IntegrityError: - await self.session.rollback() - raise - except Exception: - await self.session.rollback() - raise - return knowledge - - async def update_knowledge( - self, - knowledge: KnowledgeDB, - payload: Knowledge | KnowledgeUpdate | dict[str, Any], - ) -> KnowledgeDB: - try: - logger.debug(f"updating {knowledge.id} with payload {payload}") - if isinstance(payload, dict): - update_data = payload - else: - update_data = payload.model_dump(exclude_unset=True) - for field in update_data: - setattr(knowledge, field, update_data[field]) - - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - return knowledge - except IntegrityError as e: - await self.session.rollback() - logger.error(f"Error updating knowledge {e}") - raise KnowledgeUpdateError - - async def insert_knowledge_brain( - self, knowledge: KnowledgeDB, brain_id: UUID - ) -> KnowledgeDB: - logger.debug(f"Inserting knowledge {knowledge}") - query = select(Brain).where(Brain.brain_id == brain_id) - result = await self.session.exec(query) - brain = result.first() - logger.debug(f"Found associated brain: {brain}") - if not brain: - raise HTTPException(404, "Brain not found") - try: - knowledge.brains.append(brain) - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - except IntegrityError: - await self.session.rollback() - raise - except Exception: - await self.session.rollback() - raise - return knowledge - - async def link_to_brain( - self, knowledge: KnowledgeDB, brain_id: UUID - ) -> KnowledgeDB: - logger.debug(f"Linking knowledge {knowledge.id} to {brain_id}") - brain = await self.get_brain_by_id(brain_id) - knowledge.brains.append(brain) - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - return knowledge - - async def remove_knowledge_from_brain( - self, knowledge_id: UUID, brain_id: UUID - ) -> KnowledgeDB: - knowledge = await self.get_knowledge_by_id(knowledge_id) - brain = await self.get_brain_by_id(brain_id) - existing_brains = await knowledge.awaitable_attrs.brains - existing_brains.remove(brain) - knowledge.brains = existing_brains - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - return knowledge - - async def remove_knowledge(self, knowledge: KnowledgeDB) -> DeleteKnowledgeResponse: - assert knowledge.id - await self.session.delete(knowledge) - await self.session.commit() - return DeleteKnowledgeResponse( - status="deleted", knowledge_id=knowledge.id, file_name=knowledge.file_name - ) - - async def remove_knowledge_by_id( - self, knowledge_id: UUID - ) -> DeleteKnowledgeResponse: - query = select(KnowledgeDB).where(KnowledgeDB.id == knowledge_id) - result = await self.session.exec(query) - knowledge = result.first() - - if not knowledge: - raise NoResultFound("Knowledge not found") - - await self.session.delete(knowledge) - await self.session.commit() - assert isinstance(knowledge.file_name, str), "file_name should be a string" - return DeleteKnowledgeResponse( - file_name=knowledge.file_name, - status="deleted", - knowledge_id=knowledge_id, - ) - - async def get_knowledge_by_sync_id(self, sync_id: int) -> KnowledgeDB: - query = select(KnowledgeDB).where( - text(f"metadata->>'sync_file_id' = '{str(sync_id)}'") - ) - result = await self.session.exec(query) - knowledge = result.first() - - if not knowledge: - raise HTTPException(404, "Knowledge not found") - - return knowledge - - async def get_knowledge_by_file_name_brain_id( - self, file_name: str, brain_id: UUID - ) -> KnowledgeDB: - query = ( - select(KnowledgeDB) - .where(KnowledgeDB.file_name == file_name) - .where(KnowledgeDB.brains.any(brain_id=brain_id)) # type: ignore - ) - - result = await self.session.exec(query) - knowledge = result.first() - if not knowledge: - raise NoResultFound("Knowledge not found") - - return knowledge - - async def get_knowledge_by_sha1(self, sha1: str) -> KnowledgeDB: - query = select(KnowledgeDB).where(KnowledgeDB.file_sha1 == sha1) - result = await self.session.exec(query) - knowledge = result.first() - - if not knowledge: - raise NoResultFound("Knowledge not found") - - return knowledge - - async def get_all_children(self, parent_id: UUID) -> list[KnowledgeDB]: - query = text(""" - WITH RECURSIVE knowledge_tree AS ( - SELECT * - FROM knowledge - WHERE parent_id = :parent_id - UNION ALL - SELECT k.* - FROM knowledge k - JOIN knowledge_tree kt ON k.parent_id = kt.id - ) - SELECT * FROM knowledge_tree - """) - - result = await self.session.execute(query, params={"parent_id": parent_id}) - rows = result.fetchall() - knowledge_list = [] - for row in rows: - knowledge = KnowledgeDB( - id=row.id, - parent_id=row.parent_id, - file_name=row.file_name, - url=row.url, - extension=row.extension, - status=row.status, - source=row.source, - source_link=row.source_link, - file_size=row.file_size, - file_sha1=row.file_sha1, - created_at=row.created_at, - updated_at=row.updated_at, - metadata_=row.metadata, - is_folder=row.is_folder, - user_id=row.user_id, - ) - knowledge_list.append(knowledge) - - return knowledge_list - - async def get_root_knowledge_user(self, user_id: UUID) -> list[KnowledgeDB]: - query = ( - select(KnowledgeDB) - .where(KnowledgeDB.parent_id.is_(None)) # type: ignore - .where(KnowledgeDB.user_id == user_id) - .options(joinedload(KnowledgeDB.parent), joinedload(KnowledgeDB.children)) # type: ignore - ) - result = await self.session.exec(query) - kms = result.unique().all() - return list(kms) - - async def get_knowledge_by_id( - self, knowledge_id: UUID, user_id: UUID | None = None - ) -> KnowledgeDB: - query = ( - select(KnowledgeDB) - .where(KnowledgeDB.id == knowledge_id) - .options(joinedload(KnowledgeDB.parent), joinedload(KnowledgeDB.children)) # type: ignore - ) - if user_id: - query = query.where(KnowledgeDB.user_id == user_id) - result = await self.session.exec(query) - knowledge = result.first() - if not knowledge: - raise KnowledgeNotFoundException("Knowledge not found") - return knowledge - - async def get_brain_by_id( - self, brain_id: UUID, get_knowledge: bool = False - ) -> Brain: - query = select(Brain).where(Brain.brain_id == brain_id) - if get_knowledge: - query = query.options( - joinedload(Brain.knowledges).joinedload(KnowledgeDB.brains) - ) - - result = await self.session.exec(query) - brain = result.first() - if not brain: - raise NoResultFound("Brain not found") - return brain - - async def remove_all_knowledges_from_brain(self, brain_id) -> int: - """ - Remove all knowledge in a brain - Args: - brain_id (UUID): The id of the brain - """ - brain = await self.get_brain_by_id(brain_id) - all_knowledge = await brain.awaitable_attrs.knowledges - knowledge_to_delete_list = [ - knowledge.knowledge.source_link - for knowledge in all_knowledge - if knowledge.source == "local" - ] - - if knowledge_to_delete_list: - # FIXME: Can we bypass db ? @Amine - self.db.storage.from_("quivr").remove(knowledge_to_delete_list) - - for item in all_knowledge: - await self.session.delete(item) - await self.session.commit() - await self.session.refresh(brain) - return len(knowledge_to_delete_list) - - async def update_status_knowledge( - self, knowledge_id: UUID, status: KnowledgeStatus - ) -> KnowledgeDB | None: - try: - query = select(KnowledgeDB).where(KnowledgeDB.id == knowledge_id) - result = await self.session.exec(query) - knowledge = result.first() - if not knowledge: - raise NoResultFound("Knowledge not found") - - knowledge.status = status - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - return knowledge - except Exception: - await self.session.rollback() - raise NoResultFound("Knowledge not found") - - async def update_source_link_knowledge( - self, knowledge_id: UUID, source_link: str - ) -> KnowledgeDB: - query = select(KnowledgeDB).where(KnowledgeDB.id == knowledge_id) - result = await self.session.exec(query) - knowledge = result.first() - - if not knowledge: - raise NoResultFound("Knowledge not found") - - knowledge.source_link = source_link - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - - return knowledge - - async def update_file_sha1_knowledge( - self, knowledge_id: UUID, file_sha1: str - ) -> KnowledgeDB | None: - query = select(KnowledgeDB).where(KnowledgeDB.id == knowledge_id) - result = await self.session.exec(query) - knowledge = result.first() - - if not knowledge: - raise ValueError("Knowledge not found") - - try: - knowledge.file_sha1 = file_sha1 - self.session.add(knowledge) - await self.session.commit() - await self.session.refresh(knowledge) - return knowledge - except IntegrityError: - await self.session.rollback() - raise FileExistsError( - f"File {knowledge_id} already exists maybe under another file_name" - ) - - async def get_all_knowledge(self) -> Sequence[KnowledgeDB]: - query = select(KnowledgeDB) - result = await self.session.exec(query) - return result.all() diff --git a/backend/api/quivr_api/modules/knowledge/repository/storage.py b/backend/api/quivr_api/modules/knowledge/repository/storage.py deleted file mode 100644 index ad35659db..000000000 --- a/backend/api/quivr_api/modules/knowledge/repository/storage.py +++ /dev/null @@ -1,88 +0,0 @@ -import mimetypes -from io import BufferedReader, FileIO - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import get_supabase_async_client -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB -from quivr_api.modules.knowledge.repository.storage_interface import StorageInterface - -logger = get_logger(__name__) - - -class SupabaseS3Storage(StorageInterface): - def __init__(self): - self.client = None - - async def _set_client(self): - if self.client is None: - self.client = await get_supabase_async_client() - - def get_storage_path( - self, - knowledge: KnowledgeDB, - ) -> str: - if knowledge.id is None: - raise ValueError("knowledge should have a valid id") - return str(knowledge.id) - - async def upload_file_storage( - self, - knowledge: KnowledgeDB, - knowledge_data: FileIO | BufferedReader | bytes, - upsert: bool = False, - ): - await self._set_client() - assert self.client - - mime_type = "application/html" - if knowledge.file_name: - guessed_mime_type, _ = mimetypes.guess_type(knowledge.file_name) - mime_type = guessed_mime_type or mime_type - - storage_path = self.get_storage_path(knowledge) - logger.info( - f"Uploading file to s3://quivr/{storage_path} using supabase. upsert={upsert}, mimetype={mime_type}" - ) - - if upsert: - _ = await self.client.storage.from_("quivr").update( - storage_path, - knowledge_data, - file_options={ - "content-type": mime_type, - "upsert": "true", - "cache-control": "3600", - }, - ) - return storage_path - else: - # check if file sha1 is already in storage - try: - _ = await self.client.storage.from_("quivr").upload( - storage_path, - knowledge_data, - file_options={ - "content-type": mime_type, - "upsert": "false", - "cache-control": "3600", - }, - ) - return storage_path - - except Exception as e: - if "The resource already exists" in str(e) and not upsert: - raise FileExistsError(f"File {storage_path} already exists") - raise e - - async def remove_file(self, storage_path: str): - """ - Remove file from storage - """ - await self._set_client() - assert self.client - try: - response = await self.client.storage.from_("quivr").remove([storage_path]) - return response - except Exception as e: - logger.error(e) - raise e diff --git a/backend/api/quivr_api/modules/knowledge/repository/storage_interface.py b/backend/api/quivr_api/modules/knowledge/repository/storage_interface.py deleted file mode 100644 index bd5a3debc..000000000 --- a/backend/api/quivr_api/modules/knowledge/repository/storage_interface.py +++ /dev/null @@ -1,26 +0,0 @@ -from abc import ABC, abstractmethod -from io import BufferedReader, FileIO - -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB - - -class StorageInterface(ABC): - @abstractmethod - def get_storage_path( - self, - knowledge: KnowledgeDB, - ) -> str: - pass - - @abstractmethod - async def upload_file_storage( - self, - knowledge: KnowledgeDB, - knowledge_data: FileIO | BufferedReader | bytes, - upsert: bool = False, - ): - pass - - @abstractmethod - async def remove_file(self, storage_path: str): - pass diff --git a/backend/api/quivr_api/modules/knowledge/service/__init__.py b/backend/api/quivr_api/modules/knowledge/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/knowledge/service/knowledge_exceptions.py b/backend/api/quivr_api/modules/knowledge/service/knowledge_exceptions.py deleted file mode 100644 index c95cefa45..000000000 --- a/backend/api/quivr_api/modules/knowledge/service/knowledge_exceptions.py +++ /dev/null @@ -1,34 +0,0 @@ -class KnowledgeException(Exception): - def __init__(self, message="A knowledge-related error occurred"): - self.message = message - super().__init__(self.message) - - -class UploadError(KnowledgeException): - def __init__(self, message="An error occurred while uploading"): - super().__init__(message) - - -class KnowledgeCreationError(KnowledgeException): - def __init__(self, message="An error occurred while creating"): - super().__init__(message) - - -class KnowledgeUpdateError(KnowledgeException): - def __init__(self, message="An error occurred while updating"): - super().__init__(message) - - -class KnowledgeDeleteError(KnowledgeException): - def __init__(self, message="An error occurred while deleting"): - super().__init__(message) - - -class KnowledgeForbiddenAccess(KnowledgeException): - def __init__(self, message="You do not have permission to access this knowledge."): - super().__init__(message) - - -class KnowledgeNotFoundException(KnowledgeException): - def __init__(self, message="The requested knowledge was not found"): - super().__init__(message) diff --git a/backend/api/quivr_api/modules/knowledge/service/knowledge_service.py b/backend/api/quivr_api/modules/knowledge/service/knowledge_service.py deleted file mode 100644 index cb36c4ef8..000000000 --- a/backend/api/quivr_api/modules/knowledge/service/knowledge_service.py +++ /dev/null @@ -1,339 +0,0 @@ -import asyncio -import io -from typing import Any, List -from uuid import UUID - -from fastapi import UploadFile -from quivr_core.models import KnowledgeStatus -from sqlalchemy.exc import NoResultFound - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseService -from quivr_api.modules.knowledge.dto.inputs import ( - AddKnowledge, - CreateKnowledgeProperties, -) -from quivr_api.modules.knowledge.dto.outputs import DeleteKnowledgeResponse -from quivr_api.modules.knowledge.entity.knowledge import ( - Knowledge, - KnowledgeDB, - KnowledgeSource, - KnowledgeUpdate, -) -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.repository.storage import SupabaseS3Storage -from quivr_api.modules.knowledge.repository.storage_interface import StorageInterface -from quivr_api.modules.knowledge.service.knowledge_exceptions import ( - KnowledgeDeleteError, - KnowledgeForbiddenAccess, - UploadError, -) -from quivr_api.modules.sync.entity.sync_models import ( - DBSyncFile, - DownloadedSyncFile, - SyncFile, -) -from quivr_api.modules.upload.service.upload_file import check_file_exists - -logger = get_logger(__name__) - - -class KnowledgeService(BaseService[KnowledgeRepository]): - repository_cls = KnowledgeRepository - - def __init__( - self, - repository: KnowledgeRepository, - storage: StorageInterface = SupabaseS3Storage(), - ): - self.repository = repository - self.storage = storage - - async def get_knowledge_sync(self, sync_id: int) -> Knowledge: - km_db = await self.repository.get_knowledge_by_sync_id(sync_id) - assert km_db.id, "Knowledge ID not generated" - km = await km_db.to_dto() - return km - - # TODO: this is temporary fix for getting knowledge path. - # KM storage path should be unrelated to brain - async def get_knowledge_storage_path( - self, file_name: str, brain_id: UUID - ) -> str | None: - try: - km = await self.repository.get_knowledge_by_file_name_brain_id( - file_name, brain_id - ) - brains = await km.awaitable_attrs.brains - return next( - f"{b.brain_id}/{file_name}" - for b in brains - if check_file_exists(str(b.brain_id), file_name) - ) - except NoResultFound: - raise FileNotFoundError(f"No knowledge for file_name: {file_name}") - - async def list_knowledge( - self, knowledge_id: UUID | None, user_id: UUID | None = None - ) -> list[KnowledgeDB]: - if knowledge_id is not None: - km = await self.repository.get_knowledge_by_id(knowledge_id, user_id) - return km.children - else: - if user_id is None: - raise KnowledgeForbiddenAccess( - "can't get root knowledges without user_id" - ) - return await self.repository.get_root_knowledge_user(user_id) - - async def get_knowledge( - self, knowledge_id: UUID, user_id: UUID | None = None - ) -> KnowledgeDB: - return await self.repository.get_knowledge_by_id(knowledge_id, user_id) - - async def update_knowledge( - self, - knowledge: KnowledgeDB, - payload: Knowledge | KnowledgeUpdate | dict[str, Any], - ): - return await self.repository.update_knowledge(knowledge, payload) - - # TODO: Remove all of this - # TODO (@aminediro): Replace with ON CONFLICT smarter query... - # there is a chance of race condition but for now we let it crash in worker - # the tasks will be dealt with on retry - async def update_sha1_conflict( - self, knowledge: KnowledgeDB, brain_id: UUID, file_sha1: str - ) -> bool: - assert knowledge.id - knowledge.file_sha1 = file_sha1 - - try: - existing_knowledge = await self.repository.get_knowledge_by_sha1( - knowledge.file_sha1 - ) - logger.debug("The content of the knowledge already exists in the brain. ") - # Get existing knowledge sha1 and brains - if ( - existing_knowledge.status == KnowledgeStatus.UPLOADED - or existing_knowledge.status == KnowledgeStatus.PROCESSING - ): - existing_brains = await existing_knowledge.awaitable_attrs.brains - if brain_id in [b.brain_id for b in existing_brains]: - logger.debug("Added file to brain that already has the knowledge") - raise FileExistsError( - f"Existing file in brain {brain_id} with name {existing_knowledge.file_name}" - ) - else: - await self.repository.link_to_brain(existing_knowledge, brain_id) - await self.remove_knowledge_brain(brain_id, knowledge.id) - return False - else: - logger.debug(f"Removing previous errored file {existing_knowledge.id}") - assert existing_knowledge.id - await self.remove_knowledge_brain(brain_id, existing_knowledge.id) - await self.update_file_sha1_knowledge(knowledge.id, knowledge.file_sha1) - return True - except NoResultFound: - logger.debug( - f"First knowledge with sha1. Updating file_sha1 of {knowledge.id}" - ) - await self.update_file_sha1_knowledge(knowledge.id, knowledge.file_sha1) - return True - - async def create_knowledge( - self, - user_id: UUID, - knowledge_to_add: AddKnowledge, - upload_file: UploadFile | None = None, - ) -> KnowledgeDB: - knowledgedb = KnowledgeDB( - user_id=user_id, - file_name=knowledge_to_add.file_name, - is_folder=knowledge_to_add.is_folder, - url=knowledge_to_add.url, - extension=knowledge_to_add.extension, - source=knowledge_to_add.source, - source_link=knowledge_to_add.source_link, - file_size=upload_file.size if upload_file else 0, - metadata_=knowledge_to_add.metadata, # type: ignore - status=KnowledgeStatus.RESERVED, - parent_id=knowledge_to_add.parent_id, - ) - knowledge_db = await self.repository.create_knowledge(knowledgedb) - try: - if knowledgedb.source == KnowledgeSource.LOCAL and upload_file: - # NOTE(@aminediro): Unnecessary mem buffer because supabase doesnt accept FileIO.. - buff_reader = io.BufferedReader(upload_file.file) # type: ignore - storage_path = await self.storage.upload_file_storage( - knowledgedb, buff_reader - ) - knowledgedb.source_link = storage_path - knowledge_db = await self.repository.update_knowledge( - knowledge_db, - KnowledgeUpdate(status=KnowledgeStatus.UPLOADED), # type: ignore - ) - return knowledge_db - except Exception as e: - logger.exception( - f"Error uploading knowledge {knowledgedb.id} to storage : {e}" - ) - await self.repository.remove_knowledge(knowledge=knowledge_db) - raise UploadError() - - async def insert_knowledge_brain( - self, - user_id: UUID, - knowledge_to_add: CreateKnowledgeProperties, # FIXME: (later) @Amine brain id should not be in CreateKnowledgeProperties but since storage is brain_id/file_name - ) -> Knowledge: - knowledge = KnowledgeDB( - file_name=knowledge_to_add.file_name, - url=knowledge_to_add.url, - extension=knowledge_to_add.extension, - status=knowledge_to_add.status.value, - source=knowledge_to_add.source, - source_link=knowledge_to_add.source_link, - file_size=knowledge_to_add.file_size, - file_sha1=knowledge_to_add.file_sha1, - metadata_=knowledge_to_add.metadata, # type: ignore - user_id=user_id, - ) - - knowledge_db = await self.repository.insert_knowledge_brain( - knowledge, brain_id=knowledge_to_add.brain_id - ) - - assert knowledge_db.id, "Knowledge ID not generated" - inserted_knowledge = await knowledge_db.to_dto() - return inserted_knowledge - - async def get_all_knowledge_in_brain(self, brain_id: UUID) -> List[Knowledge]: - brain = await self.repository.get_brain_by_id(brain_id, get_knowledge=True) - all_knowledges: List[KnowledgeDB] = await brain.awaitable_attrs.knowledges - knowledges = [ - await knowledge.to_dto(get_children=False, get_parent=False) - for knowledge in all_knowledges - ] - - return knowledges - - async def update_status_knowledge( - self, - knowledge_id: UUID, - status: KnowledgeStatus, - brain_id: UUID | None = None, - ): - knowledge = await self.repository.update_status_knowledge(knowledge_id, status) - assert knowledge, "Knowledge not found" - if status == KnowledgeStatus.ERROR and brain_id: - assert isinstance(knowledge.file_name, str), "file_name should be a string" - file_name_with_brain_id = f"{brain_id}/{knowledge.file_name}" - try: - await self.storage.remove_file(file_name_with_brain_id) - except Exception as e: - logger.error( - f"Error while removing file {file_name_with_brain_id}: {e}" - ) - - return knowledge - - async def update_file_sha1_knowledge(self, knowledge_id: UUID, file_sha1: str): - return await self.repository.update_file_sha1_knowledge(knowledge_id, file_sha1) - - async def remove_knowledge(self, knowledge: KnowledgeDB) -> DeleteKnowledgeResponse: - assert knowledge.id - - try: - # TODO: - # - Notion folders are special, they are themselves files and should be removed from storage - children = await self.repository.get_all_children(knowledge.id) - km_paths = [ - self.storage.get_storage_path(k) for k in children if not k.is_folder - ] - if not knowledge.is_folder: - km_paths.append(self.storage.get_storage_path(knowledge)) - - # recursively deletes files - deleted_km = await self.repository.remove_knowledge(knowledge) - await asyncio.gather(*[self.storage.remove_file(p) for p in km_paths]) - - return deleted_km - except Exception as e: - logger.error(f"Error while remove knowledge : {e}") - raise KnowledgeDeleteError - - async def remove_knowledge_brain( - self, - brain_id: UUID, - knowledge_id: UUID, # FIXME: @amine when name in storage change no need for brain id - ) -> DeleteKnowledgeResponse: - # TODO: fix KMS - # REDO ALL THIS - knowledge = await self.repository.get_knowledge_by_id(knowledge_id) - km_brains = await knowledge.awaitable_attrs.brains - if len(km_brains) > 1: - km = await self.repository.remove_knowledge_from_brain( - knowledge_id, brain_id - ) - assert km.id - return DeleteKnowledgeResponse(file_name=km.file_name, knowledge_id=km.id) - else: - message = await self.repository.remove_knowledge_by_id(knowledge_id) - file_name_with_brain_id = f"{brain_id}/{message.file_name}" - try: - await self.storage.remove_file(file_name_with_brain_id) - except Exception as e: - logger.error( - f"Error while removing file {file_name_with_brain_id}: {e}" - ) - return message - - async def remove_all_knowledges_from_brain(self, brain_id: UUID) -> None: - await self.repository.remove_all_knowledges_from_brain(brain_id) - - logger.info( - f"All knowledge in brain {brain_id} removed successfully from table" - ) - - # TODO: REDO THIS MESS !!!! - # REMOVE ALL SYNC TABLES and start from scratch - async def update_or_create_knowledge_sync( - self, - brain_id: UUID, - user_id: UUID, - file: SyncFile, - new_sync_file: DBSyncFile | None, - prev_sync_file: DBSyncFile | None, - downloaded_file: DownloadedSyncFile, - source: str, - source_link: str, - ) -> Knowledge: - sync_id = None - # TODO: THIS IS A HACK!! Remove all of this - if prev_sync_file: - prev_knowledge = await self.get_knowledge_sync(sync_id=prev_sync_file.id) - if len(prev_knowledge.brains) > 1: - await self.repository.remove_knowledge_from_brain( - prev_knowledge.id, brain_id - ) - else: - await self.repository.remove_knowledge_by_id(prev_knowledge.id) - sync_id = prev_sync_file.id - - sync_id = new_sync_file.id if new_sync_file else sync_id - knowledge_to_add = CreateKnowledgeProperties( - brain_id=brain_id, - file_name=file.name, - extension=downloaded_file.extension, - source=source, - status=KnowledgeStatus.PROCESSING, - source_link=source_link, - file_size=file.size if file.size else 0, - # FIXME (@aminediro): This is a temporary fix, redo in KMS - file_sha1=None, - metadata={"sync_file_id": str(sync_id)}, - ) - added_knowledge = await self.insert_knowledge_brain( - knowledge_to_add=knowledge_to_add, user_id=user_id - ) - return added_knowledge diff --git a/backend/api/quivr_api/modules/knowledge/tests/__init__.py b/backend/api/quivr_api/modules/knowledge/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/knowledge/tests/conftest.py b/backend/api/quivr_api/modules/knowledge/tests/conftest.py deleted file mode 100644 index 2074110f6..000000000 --- a/backend/api/quivr_api/modules/knowledge/tests/conftest.py +++ /dev/null @@ -1,67 +0,0 @@ -from io import BufferedReader, FileIO - -from quivr_api.modules.knowledge.entity.knowledge import Knowledge, KnowledgeDB -from quivr_api.modules.knowledge.repository.storage_interface import StorageInterface - - -class ErrorStorage(StorageInterface): - async def upload_file_storage( - self, - knowledge: KnowledgeDB, - knowledge_data: FileIO | BufferedReader | bytes, - upsert: bool = False, - ): - raise SystemError - - def get_storage_path( - self, - knowledge: KnowledgeDB | Knowledge, - ) -> str: - if knowledge.id is None: - raise ValueError("knowledge should have a valid id") - return str(knowledge.id) - - async def remove_file(self, storage_path: str): - raise SystemError - - -class FakeStorage(StorageInterface): - def __init__(self): - self.storage = {} - - def get_storage_path( - self, - knowledge: KnowledgeDB | Knowledge, - ) -> str: - if knowledge.id is None: - raise ValueError("knowledge should have a valid id") - return str(knowledge.id) - - async def upload_file_storage( - self, - knowledge: KnowledgeDB, - knowledge_data: FileIO | BufferedReader | bytes, - upsert: bool = False, - ): - storage_path = f"{knowledge.id}" - if not upsert and storage_path in self.storage: - raise ValueError(f"File already exists at {storage_path}") - self.storage[storage_path] = knowledge_data - return storage_path - - async def remove_file(self, storage_path: str): - if storage_path not in self.storage: - raise FileNotFoundError(f"File not found at {storage_path}") - del self.storage[storage_path] - - # Additional helper methods for testing - def get_file(self, storage_path: str) -> FileIO | BufferedReader | bytes: - if storage_path not in self.storage: - raise FileNotFoundError(f"File not found at {storage_path}") - return self.storage[storage_path] - - def knowledge_exists(self, knowledge: KnowledgeDB | Knowledge) -> bool: - return self.get_storage_path(knowledge) in self.storage - - def clear_storage(self): - self.storage.clear() diff --git a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_controller.py b/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_controller.py deleted file mode 100644 index cf6313e97..000000000 --- a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_controller.py +++ /dev/null @@ -1,74 +0,0 @@ -import json - -import pytest -import pytest_asyncio -from httpx import ASGITransport, AsyncClient -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.main import app -from quivr_api.middlewares.auth.auth_bearer import get_current_user -from quivr_api.modules.knowledge.controller.knowledge_routes import get_km_service -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.knowledge.tests.conftest import FakeStorage -from quivr_api.modules.user.entity.user_identity import User, UserIdentity - - -@pytest_asyncio.fixture(scope="function") -async def user(session: AsyncSession) -> User: - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - assert user_1.id - return user_1 - - -@pytest_asyncio.fixture(scope="function") -async def test_client(session: AsyncSession, user: User): - def default_current_user() -> UserIdentity: - assert user.id - return UserIdentity(email=user.email, id=user.id) - - async def test_service(): - storage = FakeStorage() - repository = KnowledgeRepository(session) - return KnowledgeService(repository, storage) - - app.dependency_overrides[get_current_user] = default_current_user - app.dependency_overrides[get_km_service] = test_service - # app.dependency_overrides[get_async_session] = lambda: session - - async with AsyncClient( - transport=ASGITransport(app=app), base_url="http://test" - ) as ac: - yield ac - app.dependency_overrides = {} - - -@pytest.mark.asyncio(loop_scope="session") -async def test_post_knowledge(test_client: AsyncClient): - km_data = { - "file_name": "test_file.txt", - "source": "local", - "is_folder": False, - "parent_id": None, - } - - multipart_data = { - "knowledge_data": (None, json.dumps(km_data), "application/json"), - "file": ("test_file.txt", b"Test file content", "application/octet-stream"), - } - - response = await test_client.post( - "/knowledge/", - files=multipart_data, - ) - - assert response.status_code == 200 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_add_knowledge_invalid_input(test_client): - response = await test_client.post("/knowledge/", files={}) - assert response.status_code == 422 diff --git a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_entity.py b/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_entity.py deleted file mode 100644 index 92563fe78..000000000 --- a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_entity.py +++ /dev/null @@ -1,229 +0,0 @@ -from typing import List, Tuple -from uuid import uuid4 - -import pytest -import pytest_asyncio -from quivr_core.models import KnowledgeStatus -from sqlmodel import select, text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB -from quivr_api.modules.user.entity.user_identity import User - -TestData = Tuple[Brain, List[KnowledgeDB]] - - -@pytest_asyncio.fixture(scope="function") -async def other_user(session: AsyncSession): - sql = text( - """ - INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", "phone_change_token", "phone_change_sent_at", "email_change_token_current", "email_change_confirm_status", "banned_until", "reauthentication_token", "reauthentication_sent_at", "is_sso_user", "deleted_at") VALUES - ('00000000-0000-0000-0000-000000000000', :id , 'authenticated', 'authenticated', 'other@quivr.app', '$2a$10$vwKX0eMLlrOZvxQEA3Vl4e5V4/hOuxPjGYn9QK1yqeaZxa.42Uhze', '2024-01-22 22:27:00.166861+00', NULL, '', NULL, 'e91d41043ca2c83c3be5a6ee7a4abc8a4f4fb1afc0a8453c502af931', '2024-03-05 16:22:13.780421+00', '', '', NULL, '2024-03-30 23:21:12.077887+00', '{"provider": "email", "providers": ["email"]}', '{}', NULL, '2024-01-22 22:27:00.158026+00', '2024-04-01 17:40:15.332205+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL); - """ - ) - await session.execute(sql, params={"id": uuid4()}) - - other_user = ( - await session.exec(select(User).where(User.email == "other@quivr.app")) - ).one() - return other_user - - -@pytest_asyncio.fixture(scope="function") -async def user(session): - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - return user_1 - - -@pytest_asyncio.fixture(scope="function") -async def brain(session): - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - session.add(brain_1) - await session.commit() - return brain_1 - - -@pytest_asyncio.fixture(scope="function") -async def folder(session, user): - folder = KnowledgeDB( - file_name="folder_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - - session.add(folder) - await session.commit() - await session.refresh(folder) - return folder - - -@pytest.mark.asyncio(loop_scope="session") -async def test_knowledge_default_file(session, folder, user): - km = KnowledgeDB( - file_name="test_file_1.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[], - user_id=user.id, - parent_id=folder.id, - ) - session.add(km) - await session.commit() - await session.refresh(km) - - assert not km.is_folder - - -@pytest.mark.asyncio(loop_scope="session") -async def test_knowledge_parent(session: AsyncSession, user: User): - assert user.id - - km = KnowledgeDB( - file_name="test_file_1.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[], - user_id=user.id, - ) - - folder = KnowledgeDB( - file_name="folder_1", - extension="", - is_folder=True, - status="UPLOADED", - source="local", - source_link="local", - file_size=-1, - file_sha1=None, - brains=[], - children=[km], - user_id=user.id, - ) - - session.add(folder) - await session.commit() - await session.refresh(folder) - await session.refresh(km) - - parent = await km.awaitable_attrs.parent - assert km.parent_id == folder.id, "parent_id isn't set to folder id" - assert parent.id == folder.id, "parent_id isn't set to folder id" - assert parent.is_folder - - query = select(KnowledgeDB).where(KnowledgeDB.id == folder.id) - folder = (await session.exec(query)).first() - assert folder - - children = await folder.awaitable_attrs.children - assert len(children) > 0 - - assert children[0].id == km.id - - -@pytest.mark.asyncio(loop_scope="session") -async def test_knowledge_remove_folder_cascade( - session: AsyncSession, - folder: KnowledgeDB, - user, -): - km = KnowledgeDB( - file_name="test_file_1.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[], - user_id=user.id, - parent_id=folder.id, - ) - session.add(km) - await session.commit() - await session.refresh(km) - - # Check all removed - await session.delete(folder) - await session.commit() - - statement = select(KnowledgeDB) - results = (await session.exec(statement)).unique().all() - assert results == [] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_knowledge_dto(session, user, brain): - # add folder in brain - folder = KnowledgeDB( - file_name="folder_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[brain], - children=[], - user_id=user.id, - is_folder=True, - ) - km = KnowledgeDB( - file_name="test_file_1.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - user_id=user.id, - brains=[brain], - parent=folder, - ) - session.add(km) - session.add(km) - await session.commit() - await session.refresh(km) - - km_dto = await km.to_dto() - - assert km_dto.file_name == km.file_name - assert km_dto.url == km.url - assert km_dto.extension == km.extension - assert km_dto.status == KnowledgeStatus(km.status) - assert km_dto.source == km.source - assert km_dto.source_link == km.source_link - assert km_dto.is_folder == km.is_folder - assert km_dto.file_size == km.file_size - assert km_dto.file_sha1 == km.file_sha1 - assert km_dto.updated_at == km.updated_at - assert km_dto.created_at == km.created_at - assert km_dto.metadata == km.metadata_ # type: ignor - assert km_dto.parent - assert km_dto.parent.id == folder.id - - folder_dto = await folder.to_dto() - assert folder_dto.brains[0] == brain.model_dump() - assert folder_dto.children == [await km.to_dto()] diff --git a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_service.py b/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_service.py deleted file mode 100644 index 7381b6e91..000000000 --- a/backend/api/quivr_api/modules/knowledge/tests/test_knowledge_service.py +++ /dev/null @@ -1,1022 +0,0 @@ -import os -from io import BytesIO -from typing import List, Tuple -from uuid import uuid4 - -import pytest -import pytest_asyncio -from fastapi import UploadFile -from sqlalchemy.exc import NoResultFound -from sqlmodel import select, text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.knowledge.dto.inputs import AddKnowledge, KnowledgeStatus -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB, KnowledgeUpdate -from quivr_api.modules.knowledge.entity.knowledge_brain import KnowledgeBrain -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.service.knowledge_exceptions import ( - KnowledgeNotFoundException, - KnowledgeUpdateError, - UploadError, -) -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.knowledge.tests.conftest import ErrorStorage, FakeStorage -from quivr_api.modules.upload.service.upload_file import upload_file_storage -from quivr_api.modules.user.entity.user_identity import User -from quivr_api.modules.vector.entity.vector import Vector - -TestData = Tuple[Brain, List[KnowledgeDB]] - - -@pytest_asyncio.fixture(scope="function") -async def other_user(session: AsyncSession): - sql = text( - """ - INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", "phone_change_token", "phone_change_sent_at", "email_change_token_current", "email_change_confirm_status", "banned_until", "reauthentication_token", "reauthentication_sent_at", "is_sso_user", "deleted_at") VALUES - ('00000000-0000-0000-0000-000000000000', :id , 'authenticated', 'authenticated', 'other@quivr.app', '$2a$10$vwKX0eMLlrOZvxQEA3Vl4e5V4/hOuxPjGYn9QK1yqeaZxa.42Uhze', '2024-01-22 22:27:00.166861+00', NULL, '', NULL, 'e91d41043ca2c83c3be5a6ee7a4abc8a4f4fb1afc0a8453c502af931', '2024-03-05 16:22:13.780421+00', '', '', NULL, '2024-03-30 23:21:12.077887+00', '{"provider": "email", "providers": ["email"]}', '{}', NULL, '2024-01-22 22:27:00.158026+00', '2024-04-01 17:40:15.332205+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL); - """ - ) - await session.execute(sql, params={"id": uuid4()}) - - other_user = ( - await session.exec(select(User).where(User.email == "other@quivr.app")) - ).one() - return other_user - - -@pytest_asyncio.fixture(scope="function") -async def user(session: AsyncSession) -> User: - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - assert user_1.id - return user_1 - - -@pytest_asyncio.fixture(scope="function") -async def test_data(session: AsyncSession) -> TestData: - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - assert user_1.id - # Brain data - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - - knowledge_brain_1 = KnowledgeDB( - file_name="test_file_1.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[brain_1], - user_id=user_1.id, - ) - - knowledge_brain_2 = KnowledgeDB( - file_name="test_file_2.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha2", - brains=[], - user_id=user_1.id, - ) - - session.add(brain_1) - session.add(knowledge_brain_1) - session.add(knowledge_brain_2) - await session.commit() - return brain_1, [knowledge_brain_1, knowledge_brain_2] - - -@pytest_asyncio.fixture(scope="function") -async def folder_km_nested(session: AsyncSession, user: User): - assert user.id - - nested_folder = KnowledgeDB( - file_name="folder_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - folder = KnowledgeDB( - file_name="folder_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - parent=nested_folder, - ) - - knowledge_folder = KnowledgeDB( - file_name="file.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha2", - brains=[], - user_id=user.id, - parent=folder, - ) - - session.add(nested_folder) - session.add(folder) - session.add(knowledge_folder) - await session.commit() - await session.refresh(folder) - return nested_folder - - -@pytest_asyncio.fixture(scope="function") -async def folder_km(session: AsyncSession, user: User): - assert user.id - folder = KnowledgeDB( - file_name="folder_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - - knowledge_folder = KnowledgeDB( - file_name="file.txt", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha2", - brains=[], - user_id=user.id, - parent=folder, - ) - - session.add(folder) - session.add(knowledge_folder) - await session.commit() - await session.refresh(folder) - return folder - - -@pytest.mark.asyncio(loop_scope="session") -async def test_updates_knowledge_status(session: AsyncSession, test_data: TestData): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - await repo.update_status_knowledge(knowledges[0].id, KnowledgeStatus.ERROR) - knowledge = await repo.get_knowledge_by_id(knowledges[0].id) - assert knowledge.status == KnowledgeStatus.ERROR - - -@pytest.mark.asyncio(loop_scope="session") -async def test_updates_knowledge_status_no_knowledge( - session: AsyncSession, test_data: TestData -): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - with pytest.raises(NoResultFound): - await repo.update_status_knowledge(uuid4(), KnowledgeStatus.UPLOADED) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_update_knowledge_source_link(session: AsyncSession, test_data: TestData): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - await repo.update_source_link_knowledge(knowledges[0].id, "new_source_link") - knowledge = await repo.get_knowledge_by_id(knowledges[0].id) - assert knowledge.source_link == "new_source_link" - - -@pytest.mark.asyncio(loop_scope="session") -async def test_remove_knowledge_from_brain(session: AsyncSession, test_data: TestData): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - knowledge = await repo.remove_knowledge_from_brain(knowledges[0].id, brain.brain_id) - assert brain.brain_id not in [ - b.brain_id for b in await knowledge.awaitable_attrs.brains - ] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_cascade_remove_knowledge_by_id( - session: AsyncSession, test_data: TestData -): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - await repo.remove_knowledge_by_id(knowledges[0].id) - with pytest.raises(KnowledgeNotFoundException): - await repo.get_knowledge_by_id(knowledges[0].id) - - query = select(KnowledgeBrain).where( - KnowledgeBrain.knowledge_id == knowledges[0].id - ) - result = await session.exec(query) - knowledge_brain = result.first() - assert knowledge_brain is None - - query = select(Vector).where(Vector.knowledge_id == knowledges[0].id) - result = await session.exec(query) - vector = result.first() - assert vector is None - - -@pytest.mark.asyncio(loop_scope="session") -async def test_remove_all_knowledges_from_brain( - session: AsyncSession, test_data: TestData -): - brain, knowledges = test_data - assert brain.brain_id - - # supabase_client = get_supabase_client() - # db = supabase_client - # storage = db.storage.from_("quivr") - - # storage.upload(f"{brain.brain_id}/test_file_1", b"test_content") - - repo = KnowledgeRepository(session) - service = KnowledgeService(repo) - await repo.remove_all_knowledges_from_brain(brain.brain_id) - knowledges = await service.get_all_knowledge_in_brain(brain.brain_id) - assert len(knowledges) == 0 - - # response = storage.list(path=f"{brain.brain_id}") - # assert response == [] - # FIXME @aminediro &chloedia raise an error when trying to interact with storage UnboundLocalError: cannot access local variable 'response' where it is not associated with a value - - -@pytest.mark.asyncio(loop_scope="session") -async def test_duplicate_sha1_knowledge_same_user( - session: AsyncSession, test_data: TestData -): - brain, [existing_knowledge, _] = test_data - assert brain.brain_id - assert existing_knowledge.id - assert existing_knowledge.file_sha1 - repo = KnowledgeRepository(session) - knowledge = KnowledgeDB( - file_name="test_file_2", - extension="txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1=existing_knowledge.file_sha1, - brains=[brain], - user_id=existing_knowledge.user_id, - ) - - await repo.insert_knowledge_brain(knowledge, brain.brain_id) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_duplicate_sha1_knowledge_diff_user( - session: AsyncSession, test_data: TestData, other_user: User -): - brain, knowledges = test_data - assert other_user.id - assert brain.brain_id - assert knowledges[0].id - repo = KnowledgeRepository(session) - knowledge = KnowledgeDB( - file_name="test_file_2", - extension="txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1=knowledges[0].file_sha1, - brains=[brain], - user_id=other_user.id, # random user id - ) - - result = await repo.insert_knowledge_brain(knowledge, brain.brain_id) - assert result - - -@pytest.mark.asyncio(loop_scope="session") -async def test_add_knowledge_to_brain(session: AsyncSession, test_data: TestData): - brain, knowledges = test_data - assert brain.brain_id - assert knowledges[1].id - repo = KnowledgeRepository(session) - await repo.link_to_brain(knowledges[1], brain.brain_id) - knowledge = await repo.get_knowledge_by_id(knowledges[1].id) - brains_of_knowledge = [b.brain_id for b in await knowledge.awaitable_attrs.brains] - assert brain.brain_id in brains_of_knowledge - - query = select(KnowledgeBrain).where( - KnowledgeBrain.knowledge_id == knowledges[0].id - and KnowledgeBrain.brain_id == brain.brain_id - ) - result = await session.exec(query) - knowledge_brain = result.first() - assert knowledge_brain - - -# Knowledge Service -@pytest.mark.asyncio(loop_scope="session") -async def test_get_knowledge_in_brain(session: AsyncSession, test_data: TestData): - brain, knowledges = test_data - assert brain.brain_id - repo = KnowledgeRepository(session) - service = KnowledgeService(repo) - list_knowledge = await service.get_all_knowledge_in_brain(brain.brain_id) - assert len(list_knowledge) == 1 - brains_of_knowledge = [ - b.brain_id for b in await knowledges[0].awaitable_attrs.brains - ] - assert list_knowledge[0].id == knowledges[0].id - assert list_knowledge[0].file_name == knowledges[0].file_name - assert brain.brain_id in brains_of_knowledge - - -@pytest.mark.asyncio(loop_scope="session") -async def test_should_process_knowledge_exists( - session: AsyncSession, test_data: TestData -): - brain, [existing_knowledge, _] = test_data - assert brain.brain_id - new = KnowledgeDB( - file_name="new", - extension="txt", - status="PROCESSING", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1=None, - brains=[brain], - user_id=existing_knowledge.user_id, - ) - session.add(new) - await session.commit() - await session.refresh(new) - repo = KnowledgeRepository(session) - service = KnowledgeService(repo) - assert existing_knowledge.file_sha1 - with pytest.raises(FileExistsError): - await service.update_sha1_conflict( - new, brain.brain_id, file_sha1=existing_knowledge.file_sha1 - ) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_should_process_knowledge_link_brain( - session: AsyncSession, test_data: TestData -): - repo = KnowledgeRepository(session) - service = KnowledgeService(repo) - brain, [existing_knowledge, _] = test_data - user_id = existing_knowledge.user_id - assert brain.brain_id - prev = KnowledgeDB( - file_name="prev", - extension=".txt", - status=KnowledgeStatus.UPLOADED, - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test1", - brains=[brain], - user_id=user_id, - ) - brain_2 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - session.add(brain_2) - session.add(prev) - await session.commit() - await session.refresh(prev) - await session.refresh(brain_2) - - assert prev.id - assert brain_2.brain_id - - new = KnowledgeDB( - file_name="new", - extension="txt", - status="PROCESSING", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1=None, - brains=[brain_2], - user_id=user_id, - ) - session.add(new) - await session.commit() - await session.refresh(new) - - incoming_knowledge = await new.to_dto() - assert prev.file_sha1 - - should_process = await service.update_sha1_conflict( - incoming_knowledge, brain_2.brain_id, file_sha1=prev.file_sha1 - ) - assert not should_process - - # Check prev knowledge was linked - assert incoming_knowledge.file_sha1 - prev_knowledge = await service.repository.get_knowledge_by_id(prev.id) - prev_brains = await prev_knowledge.awaitable_attrs.brains - assert {b.brain_id for b in prev_brains} == { - brain.brain_id, - brain_2.brain_id, - } - # Check new knowledge was removed - assert new.id - with pytest.raises(KnowledgeNotFoundException): - await service.repository.get_knowledge_by_id(new.id) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_should_process_knowledge_prev_error( - session: AsyncSession, test_data: TestData -): - repo = KnowledgeRepository(session) - service = KnowledgeService(repo) - brain, [existing_knowledge, _] = test_data - user_id = existing_knowledge.user_id - assert brain.brain_id - prev = KnowledgeDB( - file_name="prev", - extension="txt", - status=KnowledgeStatus.ERROR, - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test1", - brains=[brain], - user_id=user_id, - ) - session.add(prev) - await session.commit() - await session.refresh(prev) - - assert prev.id - - new = KnowledgeDB( - file_name="new", - extension="txt", - status="PROCESSING", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1=None, - brains=[brain], - user_id=user_id, - ) - session.add(new) - await session.commit() - await session.refresh(new) - - incoming_knowledge = await new.to_dto() - assert prev.file_sha1 - should_process = await service.update_sha1_conflict( - incoming_knowledge, brain.brain_id, file_sha1=prev.file_sha1 - ) - - # Checks we should process this file - assert should_process - # Previous errored file is cleaned up - with pytest.raises(KnowledgeNotFoundException): - await service.repository.get_knowledge_by_id(prev.id) - - assert new.id - new = await service.repository.get_knowledge_by_id(new.id) - assert new.file_sha1 - - -@pytest.mark.skip( - reason="Bug: UnboundLocalError: cannot access local variable 'response'" -) -@pytest.mark.asyncio(loop_scope="session") -async def test_get_knowledge_storage_path(session: AsyncSession, test_data: TestData): - _, [knowledge, _] = test_data - assert knowledge.file_name - repository = KnowledgeRepository(session) - service = KnowledgeService(repository) - brain_2 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - session.add(brain_2) - await session.commit() - await session.refresh(brain_2) - assert brain_2.brain_id - km_data = os.urandom(128) - km_path = f"{str(knowledge.brains[0].brain_id)}/{knowledge.file_name}" - await upload_file_storage(km_data, km_path) - # Link knowledge to two brains - await repository.link_to_brain(knowledge, brain_2.brain_id) - storage_path = await service.get_knowledge_storage_path( - knowledge.file_name, brain_2.brain_id - ) - assert storage_path == km_path - - -@pytest.mark.asyncio(loop_scope="session") -async def test_create_knowledge_file(session: AsyncSession, user: User): - assert user.id - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - km_to_add = AddKnowledge( - file_name="test", - source="local", - is_folder=False, - parent_id=None, - ) - km_data = BytesIO(os.urandom(128)) - - km = await service.create_knowledge( - user_id=user.id, - knowledge_to_add=km_to_add, - upload_file=UploadFile(file=km_data, size=128, filename=km_to_add.file_name), - ) - - assert km.file_name == km_to_add.file_name - assert km.id - assert km.status == KnowledgeStatus.UPLOADED - assert not km.is_folder - # km in storage - storage.knowledge_exists(km) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_create_knowledge_folder(session: AsyncSession, user: User): - assert user.id - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - km_to_add = AddKnowledge( - file_name="test", - source="local", - is_folder=True, - parent_id=None, - ) - km_data = BytesIO(os.urandom(128)) - - km = await service.create_knowledge( - user_id=user.id, - knowledge_to_add=km_to_add, - upload_file=UploadFile(file=km_data, size=128, filename=km_to_add.file_name), - ) - - assert km.file_name == km_to_add.file_name - assert km.id - # Knowledge properties - assert km.file_name == km_to_add.file_name - assert km.is_folder == km_to_add.is_folder - assert km.url == km_to_add.url - assert km.extension == km_to_add.extension - assert km.source == km_to_add.source - assert km.file_size == 128 - assert km.metadata_ == km_to_add.metadata - assert km.is_folder == km_to_add.is_folder - assert km.status == KnowledgeStatus.UPLOADED - # Knowledge was saved - assert storage.knowledge_exists(km) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_create_knowledge_upload_error(session: AsyncSession, user: User): - assert user.id - storage = ErrorStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - km_to_add = AddKnowledge( - file_name="test", - source="local", - is_folder=True, - parent_id=None, - ) - km_data = BytesIO(os.urandom(128)) - - with pytest.raises(UploadError): - await service.create_knowledge( - user_id=user.id, - knowledge_to_add=km_to_add, - upload_file=UploadFile( - file=km_data, size=128, filename=km_to_add.file_name - ), - ) - # Check removed knowledge - statement = select(KnowledgeDB) - results = (await session.exec(statement)).all() - assert results == [] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_knowledge(session: AsyncSession, folder_km: KnowledgeDB, user: User): - assert user.id - assert folder_km.id - storage = ErrorStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - result = await service.get_knowledge(folder_km.id) - assert result.id == folder_km.id - assert result.children - assert len(result.children) > 0 - assert result.children[0] == folder_km.children[0] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_knowledge_nested( - session: AsyncSession, folder_km_nested: KnowledgeDB, user: User -): - assert user.id - assert folder_km_nested.id - storage = ErrorStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - result = await service.get_knowledge(folder_km_nested.id) - assert result.id == folder_km_nested.id - assert result.children - assert len(result.children) > 0 - assert result.children[0].is_folder - assert result.children[0] == folder_km_nested.children[0] - - -@pytest.mark.asyncio(loop_scope="session") -async def test_update_knowledge_rename( - session: AsyncSession, folder_km: KnowledgeDB, user: User -): - assert user.id - assert folder_km.id - storage = ErrorStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - new_km = await service.update_knowledge( - folder_km, - KnowledgeUpdate(file_name="change_name"), # type: ignore - ) - assert new_km.file_name == "change_name" - - -@pytest.mark.asyncio(loop_scope="session") -async def test_update_knowledge_move( - session: AsyncSession, folder_km: KnowledgeDB, user: User -): - assert user.id - assert folder_km.id - folder_2 = KnowledgeDB( - file_name="folder_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - session.add(folder_2) - await session.commit() - await session.refresh(folder_2) - - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - new_km = await service.update_knowledge( - folder_km, - KnowledgeUpdate(parent_id=folder_2.id), # type: ignore - ) - assert new_km.parent_id == folder_2.id - - -@pytest.mark.asyncio(loop_scope="session") -async def test_update_knowledge_move_error(session: AsyncSession, user: User): - assert user.id - file_1 = KnowledgeDB( - file_name="file_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=False, - ) - file_2 = KnowledgeDB( - file_name="file_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=False, - ) - session.add(file_1) - session.add(file_2) - await session.commit() - await session.refresh(file_1) - await session.refresh(file_2) - - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - with pytest.raises(KnowledgeUpdateError): - await service.update_knowledge( - file_2, - KnowledgeUpdate(parent_id=file_1.id), # type: ignore - ) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_update_knowledge_multiple(session: AsyncSession, user: User): - assert user.id - file = KnowledgeDB( - file_name="file", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=None, - file_sha1=None, - user_id=user.id, - ) - folder = KnowledgeDB( - file_name="folder_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - session.add(file) - session.add(folder) - await session.commit() - await session.refresh(folder) - - storage = ErrorStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - await service.update_knowledge( - file, - KnowledgeUpdate(parent_id=folder.id, status="UPLOADED", file_sha1="sha1"), # type: ignore - ) - - km = ( - await session.exec(select(KnowledgeDB).where(KnowledgeDB.id == file.id)) - ).first() - assert km - assert km.parent_id == folder.id - assert km.status == "UPLOADED" - assert km.file_sha1 == "sha1" - - -@pytest.mark.asyncio(loop_scope="session") -async def test_remove_knowledge(session: AsyncSession, user: User): - assert user.id - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - km_to_add = AddKnowledge( - file_name="test", - source="local", - is_folder=False, - parent_id=None, - ) - km_data = BytesIO(os.urandom(128)) - - # Create the knowledge - km = await service.create_knowledge( - user_id=user.id, - knowledge_to_add=km_to_add, - upload_file=UploadFile(file=km_data, size=128, filename=km_to_add.file_name), - ) - - # Remove knowledge - response = await service.remove_knowledge(knowledge=km) - - assert response.knowledge_id == km.id - assert response.file_name == km.file_name - - assert not storage.knowledge_exists(km) - assert ( - await session.exec(select(KnowledgeDB).where(KnowledgeDB.id == km.id)) - ).first() is None - - -@pytest.mark.asyncio(loop_scope="session") -async def test_remove_knowledge_folder(session: AsyncSession, user: User): - assert user.id - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - folder_add = AddKnowledge( - file_name="folder", - source="local", - is_folder=True, - parent_id=None, - ) - - # Create the knowledge - folder = await service.create_knowledge( - user_id=user.id, knowledge_to_add=folder_add, upload_file=None - ) - file_add = AddKnowledge( - file_name="file", - source="local", - is_folder=False, - parent_id=folder.id, - ) - - km_data = BytesIO(os.urandom(128)) - file = await service.create_knowledge( - user_id=user.id, - knowledge_to_add=file_add, - upload_file=UploadFile(file=km_data, size=128, filename=file_add.file_name), - ) - assert storage.knowledge_exists(file) - - # Remove knowledge - await service.remove_knowledge(knowledge=folder) - - assert not storage.knowledge_exists(folder) - assert not storage.knowledge_exists(file) - assert ( - await session.exec(select(KnowledgeDB).where(KnowledgeDB.id == folder.id)) - ).first() is None - assert ( - await session.exec(select(KnowledgeDB).where(KnowledgeDB.id == file.id)) - ).first() is None - - -@pytest.mark.asyncio(loop_scope="session") -async def test_list_knowledge_root(session: AsyncSession, user: User): - assert user.id - root_file = KnowledgeDB( - file_name="file_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=None, - file_sha1=None, - user_id=user.id, - ) - - root_folder = KnowledgeDB( - file_name="folder", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - nested_file = KnowledgeDB( - file_name="file_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=10, - file_sha1=None, - user_id=user.id, - parent=root_folder, - ) - session.add(nested_file) - session.add(root_file) - session.add(root_folder) - await session.commit() - await session.refresh(root_folder) - await session.refresh(root_file) - await session.refresh(nested_file) - - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - root_kms = await service.list_knowledge(knowledge_id=None, user_id=user.id) - - assert len(root_kms) == 2 - assert {k.id for k in root_kms} == {root_folder.id, root_file.id} - - -@pytest.mark.asyncio(loop_scope="session") -async def test_list_knowledge(session: AsyncSession, user: User): - assert user.id - root_file = KnowledgeDB( - file_name="file_1", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=None, - file_sha1=None, - user_id=user.id, - ) - - root_folder = KnowledgeDB( - file_name="folder", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=4, - file_sha1=None, - brains=[], - children=[], - user_id=user.id, - is_folder=True, - ) - nested_file = KnowledgeDB( - file_name="file_2", - extension="", - status="UPLOADED", - source="local", - source_link="local", - file_size=10, - file_sha1=None, - user_id=user.id, - parent=root_folder, - ) - session.add(nested_file) - session.add(root_file) - session.add(root_folder) - await session.commit() - await session.refresh(root_folder) - await session.refresh(root_file) - await session.refresh(nested_file) - - storage = FakeStorage() - repository = KnowledgeRepository(session) - service = KnowledgeService(repository, storage) - - kms = await service.list_knowledge(knowledge_id=root_folder.id, user_id=user.id) - - assert len(kms) == 1 - assert kms[0].id == nested_file.id diff --git a/backend/api/quivr_api/modules/misc/controller/__init__.py b/backend/api/quivr_api/modules/misc/controller/__init__.py deleted file mode 100644 index 50e52b3d9..000000000 --- a/backend/api/quivr_api/modules/misc/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .misc_routes import misc_router diff --git a/backend/api/quivr_api/modules/misc/controller/misc_routes.py b/backend/api/quivr_api/modules/misc/controller/misc_routes.py deleted file mode 100644 index ed085fe79..000000000 --- a/backend/api/quivr_api/modules/misc/controller/misc_routes.py +++ /dev/null @@ -1,35 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import get_async_session -from sqlmodel import text -from sqlmodel.ext.asyncio.session import AsyncSession - -logger = get_logger() -misc_router = APIRouter() - - -@misc_router.get("/excp") -async def excp(): - raise ValueError - - -@misc_router.get("/") -async def root(): - """ - Root endpoint to check the status of the API. - """ - logger.info("this is a test", a=10) - return {"status": "OK"} - - -@misc_router.get("/healthz", tags=["Health"]) -async def healthz(session: AsyncSession = Depends(get_async_session)): - try: - result = await session.execute(text("SELECT 1")) - if not result: - raise HTTPException(status_code=500, detail="Database is not healthy") - except Exception as e: - logger.error(f"Error checking database health: {e}") - raise HTTPException(status_code=500, detail="Database is not healthy") - - return {"status": "ok"} diff --git a/backend/api/quivr_api/modules/models/__init__.py b/backend/api/quivr_api/modules/models/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/models/controller/__init__.py b/backend/api/quivr_api/modules/models/controller/__init__.py deleted file mode 100644 index 7e4b0c523..000000000 --- a/backend/api/quivr_api/modules/models/controller/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model_routes import model_router - -__all__ = ["model_router"] diff --git a/backend/api/quivr_api/modules/models/controller/model_routes.py b/backend/api/quivr_api/modules/models/controller/model_routes.py deleted file mode 100644 index 75b649a33..000000000 --- a/backend/api/quivr_api/modules/models/controller/model_routes.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Annotated, List - -from fastapi import APIRouter, Depends - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.models.entity.model import Model -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -logger = get_logger(__name__) -model_router = APIRouter() - -ModelServiceDep = Annotated[ModelService, Depends(get_service(ModelService))] -UserIdentityDep = Annotated[UserIdentity, Depends(get_current_user)] - - -# get all chats -@model_router.get( - "/models", - response_model=List[Model], - dependencies=[Depends(AuthBearer())], - tags=["Models"], -) -async def get_models(current_user: UserIdentityDep, model_service: ModelServiceDep): - """ - Retrieve all models for the current user. - """ - models = await model_service.get_models() - return models diff --git a/backend/api/quivr_api/modules/models/entity/model.py b/backend/api/quivr_api/modules/models/entity/model.py deleted file mode 100644 index 16e101f9e..000000000 --- a/backend/api/quivr_api/modules/models/entity/model.py +++ /dev/null @@ -1,19 +0,0 @@ -from sqlmodel import Field, SQLModel - - -class Model(SQLModel, table=True): # type: ignore - __tablename__ = "models" # type: ignore - - name: str = Field(primary_key=True) - price: int = Field(default=1) - max_input: int = Field(default=2000) - max_output: int = Field(default=1000) - description: str = Field(default="") - display_name: str = Field(default="") - image_url: str = Field(default="") - endpoint_url: str = Field(default="") - env_variable_name: str = Field(default="") - default: bool = Field(default=False) - - class Config: - arbitrary_types_allowed = True diff --git a/backend/api/quivr_api/modules/models/repository/model.py b/backend/api/quivr_api/modules/models/repository/model.py deleted file mode 100644 index 005c00a22..000000000 --- a/backend/api/quivr_api/modules/models/repository/model.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Sequence - -from quivr_api.modules.dependencies import BaseRepository, get_supabase_client -from quivr_api.modules.models.entity.model import Model -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - - -class ModelRepository(BaseRepository): - def __init__(self, session: AsyncSession): - super().__init__(session) - # TODO: for now use it instead of session - self.db = get_supabase_client() - - async def get_models(self) -> Sequence[Model]: - query = select(Model) - response = await self.session.exec(query) - return response.all() - - async def get_model(self, model_name: str) -> Model | None: - query = select(Model).where(Model.name == model_name) - response = await self.session.exec(query) - return response.first() - - async def get_default_model(self) -> Model | None: - query = select(Model).where(Model.default == True) # noqa: E712 - response = await self.session.exec(query) - return response.first() diff --git a/backend/api/quivr_api/modules/models/repository/model_interface.py b/backend/api/quivr_api/modules/models/repository/model_interface.py deleted file mode 100644 index 52e77a940..000000000 --- a/backend/api/quivr_api/modules/models/repository/model_interface.py +++ /dev/null @@ -1,26 +0,0 @@ -from abc import ABC, abstractmethod - -from quivr_api.modules.models.entity.model import Model - - -class ModelsInterface(ABC): - @abstractmethod - def get_models(self) -> list[Model]: - """ - Get all models - """ - pass - - @abstractmethod - def get_model(self, model_name: str) -> Model: - """ - Get a model by name - """ - pass - - @abstractmethod - def get_default_model(self) -> Model: - """ - Get the default model - """ - pass diff --git a/backend/api/quivr_api/modules/models/service/model_service.py b/backend/api/quivr_api/modules/models/service/model_service.py deleted file mode 100644 index 3c20c28dc..000000000 --- a/backend/api/quivr_api/modules/models/service/model_service.py +++ /dev/null @@ -1,34 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseService -from quivr_api.modules.models.entity.model import Model -from quivr_api.modules.models.repository.model import ModelRepository - -logger = get_logger(__name__) - - -class ModelService(BaseService[ModelRepository]): - repository_cls = ModelRepository - - def __init__(self, repository: ModelRepository): - self.repository = repository - - async def get_models(self) -> list[Model]: - logger.info("Getting models") - - models = await self.repository.get_models() - - return models # type: ignore - - async def get_model(self, model_name: str) -> Model | None: - logger.info(f"Getting model {model_name}") - - model = await self.repository.get_model(model_name) - - return model - - async def get_default_model(self) -> Model | None: - logger.info("Getting default model") - - model = await self.repository.get_default_model() - - return model diff --git a/backend/api/quivr_api/modules/models/tests/__init__.py b/backend/api/quivr_api/modules/models/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/models/tests/conftest.py b/backend/api/quivr_api/modules/models/tests/conftest.py deleted file mode 100644 index fa3226a77..000000000 --- a/backend/api/quivr_api/modules/models/tests/conftest.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Tuple - -import pytest -import pytest_asyncio -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.models.entity.model import Model -from quivr_api.modules.user.entity.user_identity import User - -TestData = Tuple[Model, Model, User] - - -@pytest_asyncio.fixture(scope="function") -async def test_data( - session: AsyncSession, -) -> TestData: - # User data - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - - model_1 = Model( - name="this-is-a-fake-model", price=1, max_input=4000, max_output=2000 - ) - model_2 = Model( - name="this-is-another-fake-model", price=5, max_input=8000, max_output=4000 - ) - - session.add(model_1) - session.add(model_2) - - await session.commit() - await session.refresh(user_1) - await session.refresh(model_1) - await session.refresh(model_2) - return model_1, model_2, user_1 - - -@pytest.fixture -def sample_models(): - return [ - Model(name="gpt-3.5-turbo", price=1, max_input=4000, max_output=2000), - Model(name="gpt-4", price=5, max_input=8000, max_output=4000), - ] diff --git a/backend/api/quivr_api/modules/models/tests/test_models.py b/backend/api/quivr_api/modules/models/tests/test_models.py deleted file mode 100644 index 43a31bc13..000000000 --- a/backend/api/quivr_api/modules/models/tests/test_models.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from quivr_api.modules.models.entity.model import Model - - -def test_model_creation(): - model = Model(name="test-model", price=2, max_input=1000, max_output=500) - assert model.name == "test-model" - assert model.price == 2 - assert model.max_input == 1000 - assert model.max_output == 500 - - -@pytest.mark.asyncio(loop_scope="session") # noqa: F821 -async def test_model_attributes(test_data): - model = test_data[0] - assert hasattr(model, "name") - assert hasattr(model, "price") - assert hasattr(model, "max_input") - assert hasattr(model, "max_output") - - -def test_model_default_values(): - default_model = Model(name="default-model") - assert default_model.name == "default-model" - assert default_model.price == 1 - assert default_model.max_input == 2000 - assert default_model.max_output == 1000 - - -def test_model_comparison(): - model1 = Model(name="model1", price=2, max_input=3000, max_output=1500) - model2 = Model(name="model2", price=3, max_input=4000, max_output=2000) - model3 = Model(name="model1", price=2, max_input=3000, max_output=1500) - - assert model1 != model2 - assert model1 == model3 diff --git a/backend/api/quivr_api/modules/models/tests/test_models_service.py b/backend/api/quivr_api/modules/models/tests/test_models_service.py deleted file mode 100644 index 7b5707baf..000000000 --- a/backend/api/quivr_api/modules/models/tests/test_models_service.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest - -from quivr_api.modules.models.repository.model import ModelRepository -from quivr_api.modules.models.service.model_service import ModelService - - -@pytest.mark.asyncio(loop_scope="session") -async def test_service_get_chat_models(session): - repo = ModelRepository(session) - service = ModelService(repo) - models = await service.get_models() - assert len(models) >= 1 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_service_get_non_existing_chat_model(session): - repo = ModelRepository(session) - service = ModelService(repo) - model = await service.get_model("gpt-3.5-turbo") - assert model is None - - -@pytest.mark.asyncio(loop_scope="session") -async def test_service_get_existing_chat_model(session): - repo = ModelRepository(session) - service = ModelService(repo) - models = await service.get_models() - assert len(models) >= 1 - model = models[0] - model_get = await service.get_model(model.name) - assert model_get is not None - assert model_get == model diff --git a/backend/api/quivr_api/modules/notification/__init__.py b/backend/api/quivr_api/modules/notification/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/notification/controller/__init__.py b/backend/api/quivr_api/modules/notification/controller/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/notification/dto/__init__.py b/backend/api/quivr_api/modules/notification/dto/__init__.py deleted file mode 100644 index 2d81927d4..000000000 --- a/backend/api/quivr_api/modules/notification/dto/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .inputs import NotificationUpdatableProperties diff --git a/backend/api/quivr_api/modules/notification/dto/inputs.py b/backend/api/quivr_api/modules/notification/dto/inputs.py deleted file mode 100644 index b2c68dc8e..000000000 --- a/backend/api/quivr_api/modules/notification/dto/inputs.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel - -from quivr_api.logger import get_logger -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum - -logger = get_logger("notification") - - -class CreateNotification(BaseModel): - """Properties that can be received on notification creation""" - - user_id: UUID - status: NotificationsStatusEnum - title: str - description: Optional[str] = None - bulk_id: Optional[UUID] = None - category: Optional[str] = None - brain_id: Optional[str] = None - - def model_dump(self, *args, **kwargs): - notification_dict = super().model_dump(*args, **kwargs) - logger.debug("Notification Dict: %s", notification_dict) - - notification_dict["user_id"] = str(notification_dict["user_id"]) - if "bulk_id" in notification_dict: - notification_dict["bulk_id"] = str(notification_dict["bulk_id"]) - return notification_dict - - -class NotificationUpdatableProperties(BaseModel): - """Properties that can be received on notification update""" - - status: Optional[NotificationsStatusEnum] - description: Optional[str] diff --git a/backend/api/quivr_api/modules/notification/dto/outputs.py b/backend/api/quivr_api/modules/notification/dto/outputs.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/notification/entity/__init__.py b/backend/api/quivr_api/modules/notification/entity/__init__.py deleted file mode 100644 index 209eacf84..000000000 --- a/backend/api/quivr_api/modules/notification/entity/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .notification import Notification diff --git a/backend/api/quivr_api/modules/notification/entity/notification.py b/backend/api/quivr_api/modules/notification/entity/notification.py deleted file mode 100644 index b658d9cb8..000000000 --- a/backend/api/quivr_api/modules/notification/entity/notification.py +++ /dev/null @@ -1,27 +0,0 @@ -from datetime import datetime -from enum import Enum -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel - - -class NotificationsStatusEnum(str, Enum): - INFO = "info" - SUCCESS = "success" - WARNING = "warning" - ERROR = "error" - - -class Notification(BaseModel): - id: UUID - user_id: UUID - status: NotificationsStatusEnum - title: str - bulk_id: Optional[UUID] = None - description: Optional[str] - archived: Optional[bool] = False - read: Optional[bool] = False - datetime: Optional[datetime] # timestamp - category: Optional[str] - brain_id: Optional[str] diff --git a/backend/api/quivr_api/modules/notification/repository/__init__.py b/backend/api/quivr_api/modules/notification/repository/__init__.py deleted file mode 100644 index 2d49d9356..000000000 --- a/backend/api/quivr_api/modules/notification/repository/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .notifications import Notifications diff --git a/backend/api/quivr_api/modules/notification/repository/notifications.py b/backend/api/quivr_api/modules/notification/repository/notifications.py deleted file mode 100644 index cfc377b8d..000000000 --- a/backend/api/quivr_api/modules/notification/repository/notifications.py +++ /dev/null @@ -1,70 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.modules.notification.dto.inputs import CreateNotification -from quivr_api.modules.notification.entity.notification import Notification -from quivr_api.modules.notification.repository.notifications_interface import ( - NotificationInterface, -) - -logger = get_logger(__name__) - - -class Notifications(NotificationInterface): - def __init__(self, supabase_client): - self.db = supabase_client - - def add_notification(self, notification: CreateNotification): - """ - Add a notification - """ - response = ( - self.db.from_("notifications") - .insert(notification.model_dump(exclude_unset=True, exclude_none=True)) - .execute() - ).data - return Notification(**response[0]) - - def update_notification_by_id( - self, - notification_id, - notification, - ): - if notification_id is None: - logger.info("Notification id is required") - return None - - """Update a notification by id""" - response = ( - self.db.from_("notifications") - .update(notification.model_dump(exclude_unset=True)) - .filter("id", "eq", notification_id) - .execute() - ).data - - if response == []: - logger.info(f"Notification with id {notification_id} not found") - return None - - return Notification(**response[0]) - - def remove_notification_by_id(self, notification_id): - """ - Remove a notification by id - Args: - notification_id (UUID): The id of the notification - - Returns: - str: Status message - """ - response = ( - self.db.from_("notifications") - .delete() - .filter("id", "eq", notification_id) - .execute() - .data - ) - - if response == []: - logger.info(f"Notification with id {notification_id} not found") - return None - - return {"status": "success"} diff --git a/backend/api/quivr_api/modules/notification/repository/notifications_interface.py b/backend/api/quivr_api/modules/notification/repository/notifications_interface.py deleted file mode 100644 index e80740a82..000000000 --- a/backend/api/quivr_api/modules/notification/repository/notifications_interface.py +++ /dev/null @@ -1,36 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import UUID - -from quivr_api.modules.notification.dto.inputs import ( - CreateNotification, - NotificationUpdatableProperties, -) -from quivr_api.modules.notification.entity.notification import Notification - - -class NotificationInterface(ABC): - @abstractmethod - def add_notification(self, notification: CreateNotification) -> Notification: - """ - Add a notification - """ - pass - - @abstractmethod - def update_notification_by_id( - self, notification_id: UUID, notification: NotificationUpdatableProperties - ) -> Notification: - """Update a notification by id""" - pass - - @abstractmethod - def remove_notification_by_id(self, notification_id: UUID): - """ - Remove a notification by id - Args: - notification_id (UUID): The id of the notification - - Returns: - str: Status message - """ - pass diff --git a/backend/api/quivr_api/modules/notification/service/__init__.py b/backend/api/quivr_api/modules/notification/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/notification/service/notification_service.py b/backend/api/quivr_api/modules/notification/service/notification_service.py deleted file mode 100644 index 0e078755b..000000000 --- a/backend/api/quivr_api/modules/notification/service/notification_service.py +++ /dev/null @@ -1,42 +0,0 @@ -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.notification.dto.inputs import ( - CreateNotification, - NotificationUpdatableProperties, -) -from quivr_api.modules.notification.repository.notifications import Notifications -from quivr_api.modules.notification.repository.notifications_interface import ( - NotificationInterface, -) - - -class NotificationService: - repository: NotificationInterface - - def __init__(self, repository: NotificationInterface | None = None): - if repository is None: - supabase_client = get_supabase_client() - repository = Notifications(supabase_client) - self.repository = repository - - def add_notification(self, notification: CreateNotification): - """ - Add a notification - """ - return self.repository.add_notification(notification) - - def update_notification_by_id( - self, notification_id, notification: NotificationUpdatableProperties - ): - """ - Update a notification - """ - if notification: - return self.repository.update_notification_by_id( - notification_id, notification - ) - - def remove_notification_by_id(self, notification_id): - """ - Remove a notification - """ - return self.repository.remove_notification_by_id(notification_id) diff --git a/backend/api/quivr_api/modules/notification/tests/test_notification.py b/backend/api/quivr_api/modules/notification/tests/test_notification.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/onboarding/controller/__init__.py b/backend/api/quivr_api/modules/onboarding/controller/__init__.py deleted file mode 100644 index 04c3257b2..000000000 --- a/backend/api/quivr_api/modules/onboarding/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .onboarding_routes import onboarding_router diff --git a/backend/api/quivr_api/modules/onboarding/controller/onboarding_routes.py b/backend/api/quivr_api/modules/onboarding/controller/onboarding_routes.py deleted file mode 100644 index 15f5ca388..000000000 --- a/backend/api/quivr_api/modules/onboarding/controller/onboarding_routes.py +++ /dev/null @@ -1,20 +0,0 @@ -from fastapi import APIRouter, Depends -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.user.entity.user_identity import UserIdentity - -onboarding_router = APIRouter() - - -@onboarding_router.get( - "/onboarding", - dependencies=[Depends(AuthBearer())], - tags=["Deprecated"], -) -async def get_user_onboarding_handler( - current_user: UserIdentity = Depends(get_current_user), -) -> dict: - """ - Get user onboarding information for the current user - """ - - return {"status": "Deprecated and will be removed in v0.1"} diff --git a/backend/api/quivr_api/modules/prompt/__init__.py b/backend/api/quivr_api/modules/prompt/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/prompt/controller/__init__.py b/backend/api/quivr_api/modules/prompt/controller/__init__.py deleted file mode 100644 index 225300b6f..000000000 --- a/backend/api/quivr_api/modules/prompt/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .prompt_routes import prompt_router diff --git a/backend/api/quivr_api/modules/prompt/controller/prompt_routes.py b/backend/api/quivr_api/modules/prompt/controller/prompt_routes.py deleted file mode 100644 index 82e25a4bf..000000000 --- a/backend/api/quivr_api/modules/prompt/controller/prompt_routes.py +++ /dev/null @@ -1,56 +0,0 @@ -from uuid import UUID - -from fastapi import APIRouter, Depends - -from quivr_api.middlewares.auth import AuthBearer -from quivr_api.modules.prompt.entity.prompt import ( - CreatePromptProperties, - Prompt, - PromptUpdatableProperties, -) -from quivr_api.modules.prompt.service import PromptService - -prompt_router = APIRouter() - -promptService = PromptService() - - -@prompt_router.get("/prompts", dependencies=[Depends(AuthBearer())], tags=["Prompt"]) -async def get_prompts() -> list[Prompt]: - """ - Retrieve all public prompt - """ - return promptService.get_public_prompts() - - -@prompt_router.get( - "/prompts/{prompt_id}", dependencies=[Depends(AuthBearer())], tags=["Prompt"] -) -async def get_prompt(prompt_id: UUID) -> Prompt | None: - """ - Retrieve a prompt by its id - """ - - return promptService.get_prompt_by_id(prompt_id) - - -@prompt_router.put( - "/prompts/{prompt_id}", dependencies=[Depends(AuthBearer())], tags=["Prompt"] -) -async def update_prompt( - prompt_id: UUID, prompt: PromptUpdatableProperties -) -> Prompt | None: - """ - Update a prompt by its id - """ - - return promptService.update_prompt_by_id(prompt_id, prompt) - - -@prompt_router.post("/prompts", dependencies=[Depends(AuthBearer())], tags=["Prompt"]) -async def create_prompt_route(prompt: CreatePromptProperties) -> Prompt | None: - """ - Create a prompt by its id - """ - - return promptService.create_prompt(prompt) diff --git a/backend/api/quivr_api/modules/prompt/entity/__init__.py b/backend/api/quivr_api/modules/prompt/entity/__init__.py deleted file mode 100644 index 324aeee09..000000000 --- a/backend/api/quivr_api/modules/prompt/entity/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .prompt import ( - CreatePromptProperties, - DeletePromptResponse, - Prompt, - PromptStatusEnum, - PromptUpdatableProperties, -) diff --git a/backend/api/quivr_api/modules/prompt/entity/prompt.py b/backend/api/quivr_api/modules/prompt/entity/prompt.py deleted file mode 100644 index 2a7f90ce5..000000000 --- a/backend/api/quivr_api/modules/prompt/entity/prompt.py +++ /dev/null @@ -1,53 +0,0 @@ -from enum import Enum -from typing import List, Optional -from uuid import UUID - -from pydantic import BaseModel -from sqlmodel import UUID as PGUUID -from sqlmodel import Column, Field, Relationship, SQLModel, text - - -class PromptStatusEnum(str, Enum): - private = "private" - public = "public" - - -class Prompt(SQLModel, table=True): - __tablename__ = "prompts" # type: ignore - id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - content: str | None = None - title: str | None = Field(default=None, max_length=255) - status: str = Field(default="private", max_length=255) - brain: List["Brain"] = Relationship( # type: ignore # noqa: F821 - back_populates="prompt", sa_relationship_kwargs={"lazy": "joined"} - ) - - -class CreatePromptProperties(BaseModel): - """Properties that can be received on prompt creation""" - - title: str - content: str - status: PromptStatusEnum = PromptStatusEnum.private - - -class PromptUpdatableProperties(BaseModel): - """Properties that can be received on prompt update""" - - title: Optional[str] = None - content: Optional[str] = None - status: Optional[PromptStatusEnum] = None - - -class DeletePromptResponse(BaseModel): - """Response when deleting a prompt""" - - status: str = "delete" - prompt_id: UUID diff --git a/backend/api/quivr_api/modules/prompt/repository/__init__.py b/backend/api/quivr_api/modules/prompt/repository/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/prompt/repository/prompts.py b/backend/api/quivr_api/modules/prompt/repository/prompts.py deleted file mode 100644 index dc04a36bb..000000000 --- a/backend/api/quivr_api/modules/prompt/repository/prompts.py +++ /dev/null @@ -1,103 +0,0 @@ -from fastapi import HTTPException - -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.prompt.entity.prompt import Prompt -from quivr_api.modules.prompt.repository.prompts_interface import ( - DeletePromptResponse, - PromptsInterface, -) - - -class Prompts(PromptsInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - - def create_prompt(self, prompt): - """ - Create a prompt - """ - - response = (self.db.from_("prompts").insert(prompt.dict()).execute()).data - - return Prompt(**response[0]) - - def delete_prompt_by_id(self, prompt_id): - """ - Delete a prompt by id - Args: - prompt_id (UUID): The id of the prompt - - Returns: - A dictionary containing the status of the delete and prompt_id of the deleted prompt - """ - - # Update brains where prompt_id is equal to the value to NULL - self.db.from_("brains").update({"prompt_id": None}).filter( - "prompt_id", "eq", prompt_id - ).execute() - - # Update chat_history where prompt_id is equal to the value to NULL - self.db.from_("chat_history").update({"prompt_id": None}).filter( - "prompt_id", "eq", prompt_id - ).execute() - - # Delete the prompt - response = ( - self.db.from_("prompts") - .delete() - .filter("id", "eq", prompt_id) - .execute() - .data - ) - - if response == []: - raise HTTPException(404, "Prompt not found") - - return DeletePromptResponse(status="deleted", prompt_id=prompt_id) - - def get_prompt_by_id(self, prompt_id): - """ - Get a prompt by its id - - Args: - prompt_id (UUID): The id of the prompt - - Returns: - Prompt: The prompt - """ - - response = ( - self.db.from_("prompts").select("*").filter("id", "eq", prompt_id).execute() - ).data - - if response == []: - return None - return Prompt(**response[0]) - - def get_public_prompts(self): - """ - List all public prompts - """ - - return ( - self.db.from_("prompts") - .select("*") - .filter("status", "eq", "public") - .execute() - ).data - - def update_prompt_by_id(self, prompt_id, prompt): - """Update a prompt by id""" - - response = ( - self.db.from_("prompts") - .update(prompt.dict(exclude_unset=True)) - .filter("id", "eq", prompt_id) - .execute() - ).data - - if response == []: - raise HTTPException(404, "Prompt not found") - - return Prompt(**response[0]) diff --git a/backend/api/quivr_api/modules/prompt/repository/prompts_interface.py b/backend/api/quivr_api/modules/prompt/repository/prompts_interface.py deleted file mode 100644 index f2399b7a5..000000000 --- a/backend/api/quivr_api/modules/prompt/repository/prompts_interface.py +++ /dev/null @@ -1,57 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import UUID - -from quivr_api.modules.prompt.entity import ( - CreatePromptProperties, - DeletePromptResponse, - Prompt, - PromptUpdatableProperties, -) - - -class PromptsInterface(ABC): - @abstractmethod - def create_prompt(self, prompt: CreatePromptProperties) -> Prompt: - """ - Create a prompt - """ - pass - - @abstractmethod - def delete_prompt_by_id(self, prompt_id: UUID) -> DeletePromptResponse: - """ - Delete a prompt by id - Args: - prompt_id (UUID): The id of the prompt - - Returns: - A dictionary containing the status of the delete and prompt_id of the deleted prompt - """ - pass - - @abstractmethod - def get_prompt_by_id(self, prompt_id: UUID) -> Prompt | None: - """ - Get a prompt by its id - - Args: - prompt_id (UUID): The id of the prompt - - Returns: - Prompt: The prompt - """ - pass - - @abstractmethod - def get_public_prompts(self) -> list[Prompt]: - """ - List all public prompts - """ - pass - - @abstractmethod - def update_prompt_by_id( - self, prompt_id: UUID, prompt: PromptUpdatableProperties - ) -> Prompt: - """Update a prompt by id""" - pass diff --git a/backend/api/quivr_api/modules/prompt/service/__init__.py b/backend/api/quivr_api/modules/prompt/service/__init__.py deleted file mode 100644 index 04be1eb3c..000000000 --- a/backend/api/quivr_api/modules/prompt/service/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .prompt_service import PromptService diff --git a/backend/api/quivr_api/modules/prompt/service/get_prompt_to_use.py b/backend/api/quivr_api/modules/prompt/service/get_prompt_to_use.py deleted file mode 100644 index 92e587f80..000000000 --- a/backend/api/quivr_api/modules/prompt/service/get_prompt_to_use.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Optional -from uuid import UUID - -from quivr_api.modules.brain.service.utils.get_prompt_to_use_id import ( - get_prompt_to_use_id, -) -from quivr_api.modules.prompt.service import PromptService - -promptService = PromptService() - - -def get_prompt_to_use(brain_id: Optional[UUID], prompt_id: Optional[UUID]) -> str: - prompt_to_use_id = get_prompt_to_use_id(brain_id, prompt_id) - if prompt_to_use_id is None: - return None - - return promptService.get_prompt_by_id(prompt_to_use_id) diff --git a/backend/api/quivr_api/modules/prompt/service/prompt_service.py b/backend/api/quivr_api/modules/prompt/service/prompt_service.py deleted file mode 100644 index 3bbd1158f..000000000 --- a/backend/api/quivr_api/modules/prompt/service/prompt_service.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import List -from uuid import UUID - -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.prompt.entity.prompt import ( - CreatePromptProperties, - DeletePromptResponse, - Prompt, - PromptUpdatableProperties, -) -from quivr_api.modules.prompt.repository.prompts import Prompts - - -class PromptService: - repository: Prompts - - def __init__(self): - supabase_client = get_supabase_client() - self.repository = Prompts() - - def create_prompt(self, prompt: CreatePromptProperties) -> Prompt: - return self.repository.create_prompt(prompt) - - def delete_prompt_by_id(self, prompt_id: UUID) -> DeletePromptResponse: - """ - Delete a prompt by id - Args: - prompt_id (UUID): The id of the prompt - - Returns: - Prompt: The prompt - """ - return self.repository.delete_prompt_by_id(prompt_id) - - def get_prompt_by_id(self, prompt_id: UUID) -> Prompt | None: - """ - Get a prompt by its id - - Args: - prompt_id (UUID): The id of the prompt - - Returns: - Prompt: The prompt - """ - return self.repository.get_prompt_by_id(prompt_id) - - def get_public_prompts(self) -> List[Prompt]: - """ - List all public prompts - """ - - return self.repository.get_public_prompts() - - def update_prompt_by_id( - self, prompt_id: UUID, prompt: PromptUpdatableProperties - ) -> Prompt: - """Update a prompt by id""" - - return self.repository.update_prompt_by_id(prompt_id, prompt) diff --git a/backend/api/quivr_api/modules/prompt/tests/test_prompt.py b/backend/api/quivr_api/modules/prompt/tests/test_prompt.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/rag_service/__init__.py b/backend/api/quivr_api/modules/rag_service/__init__.py deleted file mode 100644 index 2711670b3..000000000 --- a/backend/api/quivr_api/modules/rag_service/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .rag_service import RAGService - -__all__ = ["RAGService"] diff --git a/backend/api/quivr_api/modules/rag_service/rag_factory.py b/backend/api/quivr_api/modules/rag_service/rag_factory.py deleted file mode 100644 index 4f37762ae..000000000 --- a/backend/api/quivr_api/modules/rag_service/rag_factory.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Type - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.integration_brain import IntegrationEntity -from quivr_api.modules.brain.integrations.Big.Brain import BigBrain -from quivr_api.modules.brain.integrations.GPT4.Brain import GPT4Brain -from quivr_api.modules.brain.integrations.Multi_Contract.Brain import MultiContractBrain -from quivr_api.modules.brain.integrations.Notion.Brain import NotionBrain -from quivr_api.modules.brain.integrations.Proxy.Brain import ProxyBrain -from quivr_api.modules.brain.integrations.Self.Brain import SelfBrain -from quivr_api.modules.brain.integrations.SQL.Brain import SQLBrain -from quivr_api.modules.brain.knowledge_brain_qa import KnowledgeBrainQA - -logger = get_logger(__name__) - - -class RAGServiceFactory: - integration_list: dict[str, Type[KnowledgeBrainQA]] = { - "notion": NotionBrain, - "gpt4": GPT4Brain, - "sql": SQLBrain, - "big": BigBrain, - "doc": KnowledgeBrainQA, - "proxy": ProxyBrain, - "self": SelfBrain, - "multi-contract": MultiContractBrain, - } - - def get_brain_cls(self, integration: IntegrationEntity): - pass diff --git a/backend/api/quivr_api/modules/rag_service/rag_service.py b/backend/api/quivr_api/modules/rag_service/rag_service.py deleted file mode 100644 index b7d73f2fe..000000000 --- a/backend/api/quivr_api/modules/rag_service/rag_service.py +++ /dev/null @@ -1,379 +0,0 @@ -import datetime -import os -from uuid import UUID, uuid4 - -from quivr_core.brain import Brain as BrainCore -from quivr_core.chat import ChatHistory as ChatHistoryCore -from quivr_core.config import LLMEndpointConfig, RetrievalConfig -from quivr_core.llm.llm_endpoint import LLMEndpoint -from quivr_core.models import ChatLLMMetadata, ParsedRAGResponse, RAGResponseMetadata -from quivr_core.quivr_rag_langgraph import QuivrQARAGLangGraph - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import BrainEntity -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.brain.service.utils.format_chat_history import ( - format_chat_history, -) -from quivr_api.modules.chat.dto.inputs import CreateChatHistory -from quivr_api.modules.chat.dto.outputs import GetChatHistoryOutput -from quivr_api.modules.chat.service.chat_service import ChatService -from quivr_api.modules.dependencies import ( - get_embedding_client, - get_supabase_client, -) -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.prompt.entity.prompt import Prompt -from quivr_api.modules.prompt.service.prompt_service import PromptService -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.vector.service.vector_service import VectorService -from quivr_api.utils.uuid_generator import generate_uuid_from_string -from quivr_api.vectorstore.supabase import CustomSupabaseVectorStore - -from .utils import generate_source - -logger = get_logger(__name__) - - -class RAGService: - def __init__( - self, - current_user: UserIdentity, - chat_id: UUID, - model_service: ModelService, - chat_service: ChatService, - brain: BrainEntity, - retrieval_config: RetrievalConfig | None = None, - brain_service: BrainService | None = None, - prompt_service: PromptService | None = None, - knowledge_service: KnowledgeService | None = None, - vector_service: VectorService | None = None, - ): - # Services - self.brain_service = brain_service - self.prompt_service = prompt_service - self.chat_service = chat_service - self.knowledge_service = knowledge_service - self.vector_service = vector_service - self.model_service = model_service - - # Base models - self.current_user = current_user - self.chat_id = chat_id - self.brain = brain - self.prompt = ( - self.get_brain_prompt(self.brain) - if self.brain and self.brain_service - else None - ) - - self.retrieval_config = retrieval_config - - # check at init time - self.model_to_use = brain.model if brain else None - - def get_brain_prompt(self, brain: BrainEntity) -> Prompt | None: - if not self.prompt_service: - raise ValueError("PromptService not provided") - - return ( - self.prompt_service.get_prompt_by_id(brain.prompt_id) - if brain.prompt_id - else None - ) - - def _build_chat_history( - self, - history: list[GetChatHistoryOutput], - ) -> ChatHistoryCore: - transformed_history = format_chat_history(history) - chat_history = ChatHistoryCore( - brain_id=self.brain.brain_id, chat_id=self.chat_id - ) - - [chat_history.append(m) for m in transformed_history] - return chat_history - - async def _get_retrieval_config(self) -> RetrievalConfig: - if self.retrieval_config: - retrieval_config = self.retrieval_config - else: - retrieval_config = await self._build_retrieval_config() - - return retrieval_config - - async def _build_retrieval_config(self) -> RetrievalConfig: - model = await self.model_service.get_model(self.model_to_use) # type: ignore - if model is None: - raise ValueError(f"Cannot get model {self.model_to_use}") - api_key = os.getenv(model.env_variable_name, "not-defined") - - retrieval_config = RetrievalConfig( - llm_config=LLMEndpointConfig( - model=self.model_to_use, # type: ignore - llm_base_url=model.endpoint_url, - llm_api_key=api_key, - temperature=(LLMEndpointConfig.model_fields["temperature"].default), - max_input_tokens=model.max_input, - max_output_tokens=model.max_output, - ), - prompt=self.prompt.content if self.prompt else None, - ) - return retrieval_config - - def get_llm(self, retrieval_config: RetrievalConfig): - return LLMEndpoint.from_config(retrieval_config.llm_config) - - def create_vector_store( - self, brain_id: UUID, max_input: int - ) -> CustomSupabaseVectorStore: - if not self.vector_service: - raise ValueError("VectorService not provided") - - supabase_client = get_supabase_client() - embeddings = get_embedding_client() - return CustomSupabaseVectorStore( - supabase_client, - embeddings, - table_name="vectors", - brain_id=brain_id, - max_input=max_input, - vector_service=self.vector_service, - ) - - def save_answer(self, question: str, answer: ParsedRAGResponse): - metadata = answer.metadata.model_dump() if answer.metadata else {} - metadata["snippet_color"] = self.brain.snippet_color if self.brain else None - metadata["snippet_emoji"] = self.brain.snippet_emoji if self.brain else None - logger.info(f"Saving answer with metadata: {metadata}") - return self.chat_service.update_chat_history( - CreateChatHistory( - **{ - "chat_id": self.chat_id, - "user_message": question, - "assistant": answer.answer, - "brain_id": self.brain.brain_id, - # TODO: prompt_id should always be not None - "prompt_id": self.prompt.id if self.prompt else None, - "metadata": metadata, - } - ) - ) - - async def generate_answer( - self, - question: str, - ): - logger.info( - f"Creating question for chat {self.chat_id} with brain {self.brain.brain_id} " - ) - retrieval_config = await self._get_retrieval_config() - logger.debug(f"generate_answer with config : {retrieval_config.model_dump()}") - history = await self.chat_service.get_chat_history(self.chat_id) - # Format the history, sanitize the input - chat_history = self._build_chat_history(history) - - # Get list of files - list_files = ( - await self.knowledge_service.get_all_knowledge_in_brain(self.brain.brain_id) - if self.knowledge_service - else [] - ) - - # Build RAG dependencies to inject - vector_store = ( - self.create_vector_store( - self.brain.brain_id, retrieval_config.llm_config.max_input_tokens - ) - if self.vector_service - else None - ) - - llm = self.get_llm(retrieval_config) - - brain_core = BrainCore( - name=self.brain.name, - id=self.brain.id, - llm=llm, - vector_db=vector_store, - embedder=vector_store.embeddings if vector_store else None, - ) - - parsed_response = brain_core.ask( - question=question, - retrieval_config=retrieval_config, - rag_pipeline=QuivrQARAGLangGraph, - list_files=list_files, - chat_history=chat_history, - ) - - # Save the answer to db - if self.brain_service: - new_chat_entry = self.save_answer(question, parsed_response) - - # Format output to be correct - metadata = ( - parsed_response.metadata.model_dump() if parsed_response.metadata else {} - ) - metadata["snippet_color"] = self.brain.snippet_color if self.brain else None - metadata["snippet_emoji"] = self.brain.snippet_emoji if self.brain else None - return GetChatHistoryOutput( - **{ - "chat_id": self.chat_id, - "user_message": question, - "assistant": parsed_response.answer, - "message_time": new_chat_entry.message_time if new_chat_entry else None, - "prompt_title": (self.prompt.title if self.prompt else None), - "brain_name": self.brain.name if self.brain else None, - "message_id": new_chat_entry.message_id if new_chat_entry else None, - "brain_id": str(self.brain.brain_id) if self.brain else None, - "metadata": metadata, - } - ) - - async def generate_answer_stream( - self, - question: str, - ): - logger.info( - f"Creating question for chat {self.chat_id} with brain {self.brain.brain_id} " - ) - # Build the rag config - retrieval_config = await self._get_retrieval_config() - # Get chat history - history = await self.chat_service.get_chat_history(self.chat_id) - # Format the history, sanitize the input - chat_history = self._build_chat_history(history) - - # Get list of files urls - list_files = ( - await self.knowledge_service.get_all_knowledge_in_brain(self.brain.brain_id) - if self.knowledge_service - else [] - ) - - vector_store = ( - self.create_vector_store( - self.brain.brain_id, retrieval_config.llm_config.max_input_tokens - ) - if self.vector_service - else None - ) - - llm = self.get_llm(retrieval_config) - - if self.prompt: - retrieval_config.prompt = self.prompt.content - - # Get model metadata - model_metadata = await self.model_service.get_model(self.brain.name) - - brain_core = BrainCore( - name=self.brain.name, - id=self.brain.id, - llm=llm, - vector_db=vector_store, - embedder=vector_store.embeddings if vector_store else None, - ) - - full_answer = "" - - metadata = {} - metadata["snippet_color"] = self.brain.snippet_color if self.brain else None - metadata["snippet_emoji"] = self.brain.snippet_emoji if self.brain else None - message_metadata = { - "chat_id": self.chat_id, - "message_id": uuid4(), # do we need it ?, - "user_message": question, # TODO: define result - "message_time": datetime.datetime.now(), # TODO: define result - "prompt_title": (self.prompt.title if self.prompt else ""), - # brain_name and brain_id must be None in the chat-with-llm case, as this will force the front to look for the model_metadata - "brain_name": self.brain.name if self.brain_service else None, - "brain_id": self.brain.brain_id if self.brain_service else None, - } - - metadata_model = {} - if model_metadata: - metadata_model = ChatLLMMetadata( - name=self.brain.name, - description=model_metadata.description, - image_url=model_metadata.image_url, - display_name=model_metadata.display_name, - brain_id=str(generate_uuid_from_string(self.brain.name)), - brain_name=self.model_to_use, - ) - - async for response in brain_core.ask_streaming( - question=question, - retrieval_config=retrieval_config, - rag_pipeline=QuivrQARAGLangGraph, - chat_history=chat_history, - list_files=list_files, - ): - # Format output to be correct servicedf;j - if not response.last_chunk: - streamed_chat_history = GetChatHistoryOutput( - assistant=response.answer, - metadata=response.metadata.model_dump(), - **message_metadata, - ) - if streamed_chat_history.metadata: - streamed_chat_history.metadata["snippet_color"] = ( - self.brain.snippet_color if self.brain else None - ) - streamed_chat_history.metadata["snippet_emoji"] = ( - self.brain.snippet_emoji if self.brain else None - ) - if metadata_model: - streamed_chat_history.metadata["metadata_model"] = ( - metadata_model - ) - full_answer += response.answer - yield f"data: {streamed_chat_history.model_dump_json()}" - - # For last chunk parse the sources, and the full answer - streamed_chat_history = GetChatHistoryOutput( - assistant=response.answer, - metadata=response.metadata.model_dump(), - **message_metadata, - ) - - if streamed_chat_history.metadata: - streamed_chat_history.metadata["snippet_color"] = ( - self.brain.snippet_color if self.brain else None - ) - streamed_chat_history.metadata["snippet_emoji"] = ( - self.brain.snippet_emoji if self.brain else None - ) - if metadata_model: - streamed_chat_history.metadata["metadata_model"] = metadata_model - - sources_urls = ( - await generate_source( - knowledge_service=self.knowledge_service, - brain_id=self.brain.brain_id, - source_documents=response.metadata.sources, - citations=( - streamed_chat_history.metadata["citations"] - if streamed_chat_history.metadata - else None - ), - ) - if self.knowledge_service - else [] - ) - - if streamed_chat_history.metadata: - streamed_chat_history.metadata["sources"] = sources_urls - - self.save_answer( - question, - ParsedRAGResponse( - answer=full_answer, - metadata=RAGResponseMetadata.model_validate( - streamed_chat_history.metadata - ), - ), - ) - yield f"data: {streamed_chat_history.model_dump_json()}" diff --git a/backend/api/quivr_api/modules/rag_service/utils.py b/backend/api/quivr_api/modules/rag_service/utils.py deleted file mode 100644 index afc12082e..000000000 --- a/backend/api/quivr_api/modules/rag_service/utils.py +++ /dev/null @@ -1,105 +0,0 @@ -import logging -from typing import Any, List -from uuid import UUID - -from quivr_api.modules.chat.dto.chats import Sources -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.upload.service.generate_file_signed_url import ( - generate_file_signed_url, -) - -logger = logging.getLogger(__name__) - - -# TODO: REFACTOR THIS, it does call the DB , so maybe in a service -async def generate_source( - knowledge_service: KnowledgeService, - brain_id: UUID, - source_documents: List[Any] | None, - citations: List[int] | None = None, -) -> List[Sources]: - """ - Generate the sources list for the answer - It takes in a list of sources documents and citations that points to the docs index that was used in the answer - """ - # Initialize an empty list for sources - sources_list: List[Sources] = [] - - # Initialize a dictionary for storing generated URLs - generated_urls = {} - - # remove duplicate sources with same name and create a list of unique sources - sources_url_cache = {} - - # Get source documents from the result, default to an empty list if not found - # If source documents exist - if source_documents: - logger.debug(f"Citations {citations}") - for index, doc in enumerate(source_documents): - logger.debug(f"Processing source document {doc.metadata['file_name']}") - if citations is not None: - if index not in citations: - logger.debug( - f"Skipping source document {doc.metadata['file_name']}" - ) - continue - # Check if 'url' is in the document metadata - is_url = ( - "original_file_name" in doc.metadata - and doc.metadata["original_file_name"] is not None - and doc.metadata["original_file_name"].startswith("http") - ) - - # Determine the name based on whether it's a URL or a file - name = ( - doc.metadata["original_file_name"] - if is_url - else doc.metadata["file_name"] - ) - - # Determine the type based on whether it's a URL or a file - type_ = "url" if is_url else "file" - - # Determine the source URL based on whether it's a URL or a file - if is_url: - source_url = doc.metadata["original_file_name"] - else: - # Check if the URL has already been generated - try: - file_name = doc.metadata["file_name"] - file_path = await knowledge_service.get_knowledge_storage_path( - file_name=file_name, brain_id=brain_id - ) - if file_path in generated_urls: - source_url = generated_urls[file_path] - else: - # Generate the URL - if file_path in sources_url_cache: - source_url = sources_url_cache[file_path] - else: - generated_url = generate_file_signed_url(file_path) - if generated_url is not None: - source_url = generated_url.get("signedURL", "") - else: - source_url = "" - # Store the generated URL - generated_urls[file_path] = source_url - except Exception as e: - logger.error(f"Error generating file signed URL: {e}") - continue - - # Append a new Sources object to the list - sources_list.append( - Sources( - name=name, - type=type_, - source_url=source_url, - original_file_name=name, - citation=doc.page_content, - integration=doc.metadata["integration"], - integration_link=doc.metadata["integration_link"], - ) - ) - else: - logger.debug("No source documents found or source_documents is not a list.") - return sources_list diff --git a/backend/api/quivr_api/modules/sync/__init__.py b/backend/api/quivr_api/modules/sync/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/controller/__init__.py b/backend/api/quivr_api/modules/sync/controller/__init__.py deleted file mode 100644 index 494985645..000000000 --- a/backend/api/quivr_api/modules/sync/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .sync_routes import sync_router diff --git a/backend/api/quivr_api/modules/sync/controller/azure_sync_routes.py b/backend/api/quivr_api/modules/sync/controller/azure_sync_routes.py deleted file mode 100644 index d2949f9fc..000000000 --- a/backend/api/quivr_api/modules/sync/controller/azure_sync_routes.py +++ /dev/null @@ -1,153 +0,0 @@ -import os - -import requests -from fastapi import APIRouter, Depends, HTTPException, Request -from fastapi.responses import HTMLResponse -from msal import ConfidentialClientApplication - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -from .successfull_connection import successfullConnectionPage - -# Initialize logger -logger = get_logger(__name__) - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() - -# Initialize API router -azure_sync_router = APIRouter() - -# Constants -CLIENT_ID = os.getenv("SHAREPOINT_CLIENT_ID") -CLIENT_SECRET = os.getenv("SHAREPOINT_CLIENT_SECRET") -AUTHORITY = "https://login.microsoftonline.com/common" -BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") -REDIRECT_URI = f"{BACKEND_URL}/sync/azure/oauth2callback" -SCOPE = [ - "https://graph.microsoft.com/Files.Read", - "https://graph.microsoft.com/User.Read", - "https://graph.microsoft.com/Sites.Read.All", -] - - -@azure_sync_router.post( - "/sync/azure/authorize", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -def authorize_azure( - request: Request, name: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Authorize Azure sync for the current user. - - Args: - request (Request): The request object. - current_user (UserIdentity): The current authenticated user. - - Returns: - dict: A dictionary containing the authorization URL. - """ - client = ConfidentialClientApplication( - CLIENT_ID, client_credential=CLIENT_SECRET, authority=AUTHORITY - ) - logger.debug(f"Authorizing Azure sync for user: {current_user.id}") - state = f"user_id={current_user.id}, name={name}" - flow = client.initiate_auth_code_flow( - scopes=SCOPE, redirect_uri=REDIRECT_URI, state=state, prompt="select_account" - ) - - sync_user_input = SyncsUserInput( - user_id=str(current_user.id), - name=name, - provider="Azure", - credentials={}, - state={"state": state}, - additional_data={"flow": flow}, - status=str(SyncsUserStatus.SYNCING), - ) - sync_user_service.create_sync_user(sync_user_input) - return {"authorization_url": flow["auth_uri"]} - - -@azure_sync_router.get("/sync/azure/oauth2callback", tags=["Sync"]) -def oauth2callback_azure(request: Request): - """ - Handle OAuth2 callback from Azure. - - Args: - request (Request): The request object. - - Returns: - dict: A dictionary containing a success message. - """ - client = ConfidentialClientApplication( - CLIENT_ID, client_credential=CLIENT_SECRET, authority=AUTHORITY - ) - state = request.query_params.get("state") - state_split = state.split(",") - current_user = state_split[0].split("=")[1] # Extract user_id from state - name = state_split[1].split("=")[1] if state else None - state_dict = {"state": state} - logger.debug( - f"Handling OAuth2 callback for user: {current_user} with state: {state}" - ) - sync_user_state = sync_user_service.get_sync_user_by_state(state_dict) - logger.info(f"Retrieved sync user state: {sync_user_state}") - - if not sync_user_state or state_dict != sync_user_state.state: - logger.error("Invalid state parameter") - raise HTTPException(status_code=400, detail="Invalid state parameter") - if str(sync_user_state.user_id) != current_user: - logger.info(f"Sync user state: {sync_user_state}") - logger.info(f"Current user: {current_user}") - logger.info(f"Sync user state user_id: {sync_user_state.user_id}") - logger.error("Invalid user") - raise HTTPException(status_code=400, detail="Invalid user") - - result = client.acquire_token_by_auth_code_flow( - sync_user_state.additional_data["flow"], dict(request.query_params) - ) - if "access_token" not in result: - logger.error(f"Failed to acquire token: {result}") - raise HTTPException( - status_code=400, - detail=f"Failed to acquire token: {result}", - ) - - access_token = result["access_token"] - - creds = result - logger.info(f"Fetched OAuth2 token for user: {current_user}") - - # Fetch user email from Microsoft Graph API - graph_url = "https://graph.microsoft.com/v1.0/me" - headers = {"Authorization": f"Bearer {access_token}"} - response = requests.get(graph_url, headers=headers) - if response.status_code != 200: - logger.error("Failed to fetch user profile from Microsoft Graph API") - raise HTTPException(status_code=400, detail="Failed to fetch user profile") - - user_info = response.json() - user_email = user_info.get("mail") or user_info.get("userPrincipalName") - logger.info(f"Retrieved email for user: {current_user} - {user_email}") - - sync_user_input = SyncUserUpdateInput( - credentials=result, - email=user_email, - status=str(SyncsUserStatus.SYNCED), - ) - - sync_user_service.update_sync_user(current_user, state_dict, sync_user_input) - logger.info(f"Azure sync created successfully for user: {current_user}") - return HTMLResponse(successfullConnectionPage) diff --git a/backend/api/quivr_api/modules/sync/controller/dropbox_sync_routes.py b/backend/api/quivr_api/modules/sync/controller/dropbox_sync_routes.py deleted file mode 100644 index df3c955a9..000000000 --- a/backend/api/quivr_api/modules/sync/controller/dropbox_sync_routes.py +++ /dev/null @@ -1,165 +0,0 @@ -import os -from uuid import UUID - -from dropbox import Dropbox, DropboxOAuth2Flow -from fastapi import APIRouter, Depends, HTTPException, Request -from fastapi.responses import HTMLResponse - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -from .successfull_connection import successfullConnectionPage - -DROPBOX_APP_KEY = os.getenv("DROPBOX_APP_KEY") -DROPBOX_APP_SECRET = os.getenv("DROPBOX_APP_SECRET") -BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") -BASE_REDIRECT_URI = f"{BACKEND_URL}/sync/dropbox/oauth2callback" -SCOPE = ["files.metadata.read", "account_info.read", "files.content.read"] - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() - -logger = get_logger(__name__) - -# Initialize API router -dropbox_sync_router = APIRouter() - - -@dropbox_sync_router.post( - "/sync/dropbox/authorize", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -def authorize_dropbox( - request: Request, name: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Authorize DropBox sync for the current user. - - Args: - request (Request): The request object. - current_user (UserIdentity): The current authenticated user. - - Returns: - dict: A dictionary containing the authorization URL. - """ - logger.debug( - f"Authorizing Drop Box sync for user: {current_user.id}, name : {name}" - ) - auth_flow = DropboxOAuth2Flow( - DROPBOX_APP_KEY, - redirect_uri=BASE_REDIRECT_URI, - session={}, - csrf_token_session_key="csrf-token", - consumer_secret=DROPBOX_APP_SECRET, - token_access_type="offline", - scope=SCOPE, - ) - state: str = f"user_id={current_user.id}, name={name}" - authorize_url = auth_flow.start(state) - - logger.info( - f"Generated authorization URL: {authorize_url} for user: {current_user.id}" - ) - sync_user_input = SyncsUserInput( - name=name, - user_id=str(current_user.id), - provider="DropBox", - credentials={}, - state={"state": state}, - additional_data={}, - status=str(SyncsUserStatus.SYNCING), - ) - sync_user_service.create_sync_user(sync_user_input) - return {"authorization_url": authorize_url} - - -@dropbox_sync_router.get("/sync/dropbox/oauth2callback", tags=["Sync"]) -def oauth2callback_dropbox(request: Request): - """ - Handle OAuth2 callback from DropBox. - - Args: - request (Request): The request object. - - Returns: - dict: A dictionary containing a success message. - """ - state = request.query_params.get("state") - if not state: - raise HTTPException(status_code=400, detail="Invalid state parameter") - session = {} - session["csrf-token"] = state.split("|")[0] if "|" in state else "" - - logger.debug("Keys in session : %s", session.keys()) - logger.debug("Value in session : %s", session.values()) - - state = state.split("|")[1] if "|" in state else state # type: ignore - state_dict = {"state": state} - state_split = state.split(",") # type: ignore - current_user = UUID(state_split[0].split("=")[1]) if state else None - logger.debug( - f"Handling OAuth2 callback for user: {current_user} with state: {state} and state_dict: {state_dict}" - ) - sync_user_state = sync_user_service.get_sync_user_by_state(state_dict) - - if not sync_user_state or state_dict != sync_user_state.state: - logger.error("Invalid state parameter") - raise HTTPException(status_code=400, detail="Invalid state parameter") - else: - logger.info( - f"CURRENT USER: {current_user}, SYNC USER STATE USER: {sync_user_state.user_id}" - ) - - if sync_user_state.user_id != current_user: - raise HTTPException(status_code=400, detail="Invalid user") - - auth_flow = DropboxOAuth2Flow( - DROPBOX_APP_KEY, - redirect_uri=BASE_REDIRECT_URI, - session=session, - csrf_token_session_key="csrf-token", - consumer_secret=DROPBOX_APP_SECRET, - token_access_type="offline", - scope=SCOPE, - ) - try: - oauth_result = auth_flow.finish(request.query_params) - - access_token = oauth_result.access_token - - dbx = Dropbox(oauth2_access_token=access_token) - - # Get account information - account_info = dbx.users_get_current_account() - user_email = account_info.email # type: ignore - account_id = account_info.account_id # type: ignore - - result: dict[str, str] = { - "access_token": oauth_result.access_token, - "refresh_token": oauth_result.refresh_token, - "account_id": account_id, - "expires_in": str(oauth_result.expires_at), - } - - sync_user_input = SyncUserUpdateInput( - credentials=result, - # state={}, - email=user_email, - status=str(SyncsUserStatus.SYNCED), - ) - assert current_user - sync_user_service.update_sync_user(current_user, state_dict, sync_user_input) - logger.info(f"DropBox sync created successfully for user: {current_user}") - return HTMLResponse(successfullConnectionPage) - except Exception as e: - logger.error(f"Error: {e}") - raise HTTPException(status_code=400, detail="Invalid user") diff --git a/backend/api/quivr_api/modules/sync/controller/github_sync_routes.py b/backend/api/quivr_api/modules/sync/controller/github_sync_routes.py deleted file mode 100644 index fc4cd4a91..000000000 --- a/backend/api/quivr_api/modules/sync/controller/github_sync_routes.py +++ /dev/null @@ -1,164 +0,0 @@ -import os - -import requests -from fastapi import APIRouter, Depends, HTTPException, Request -from fastapi.responses import HTMLResponse - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -from .successfull_connection import successfullConnectionPage - -# Initialize logger -logger = get_logger(__name__) - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() - -# Initialize API router -github_sync_router = APIRouter() - -# Constants -CLIENT_ID = os.getenv("GITHUB_CLIENT_ID") -CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET") -BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") -REDIRECT_URI = f"{BACKEND_URL}/sync/github/oauth2callback" -SCOPE = "repo user" - - -@github_sync_router.post( - "/sync/github/authorize", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -def authorize_github( - request: Request, name: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Authorize GitHub sync for the current user. - - Args: - request (Request): The request object. - current_user (UserIdentity): The current authenticated user. - - Returns: - dict: A dictionary containing the authorization URL. - """ - logger.debug(f"Authorizing GitHub sync for user: {current_user.id}") - state = f"user_id={current_user.id},name={name}" - authorization_url = ( - f"https://github.com/login/oauth/authorize?client_id={CLIENT_ID}" - f"&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={state}" - ) - - sync_user_input = SyncsUserInput( - user_id=str(current_user.id), - name=name, - provider="GitHub", - credentials={}, - state={"state": state}, - status=str(SyncsUserStatus.SYNCING), - ) - sync_user_service.create_sync_user(sync_user_input) - return {"authorization_url": authorization_url} - - -@github_sync_router.get("/sync/github/oauth2callback", tags=["Sync"]) -def oauth2callback_github(request: Request): - """ - Handle OAuth2 callback from GitHub. - - Args: - request (Request): The request object. - - Returns: - dict: A dictionary containing a success message. - """ - state = request.query_params.get("state") - state_split = state.split(",") - current_user = state_split[0].split("=")[1] # Extract user_id from state - name = state_split[1].split("=")[1] if state else None - state_dict = {"state": state} - logger.debug( - f"Handling OAuth2 callback for user: {current_user} with state: {state}" - ) - sync_user_state = sync_user_service.get_sync_user_by_state(state_dict) - logger.info(f"Retrieved sync user state: {sync_user_state}") - - if state_dict != sync_user_state["state"]: - logger.error("Invalid state parameter") - raise HTTPException(status_code=400, detail="Invalid state parameter") - if sync_user_state.get("user_id") != current_user: - logger.error("Invalid user") - raise HTTPException(status_code=400, detail="Invalid user") - - token_url = "https://github.com/login/oauth/access_token" - data = { - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "code": request.query_params.get("code"), - "redirect_uri": REDIRECT_URI, - "state": state, - } - headers = {"Accept": "application/json"} - response = requests.post(token_url, data=data, headers=headers) - if response.status_code != 200: - logger.error(f"Failed to acquire token: {response.json()}") - raise HTTPException( - status_code=400, - detail=f"Failed to acquire token: {response.json()}", - ) - - result = response.json() - access_token = result.get("access_token") - if not access_token: - logger.error(f"Failed to acquire token: {result}") - raise HTTPException( - status_code=400, - detail=f"Failed to acquire token: {result}", - ) - - creds = result - logger.info(f"Fetched OAuth2 token for user: {current_user}") - - # Fetch user email from GitHub API - github_api_url = "https://api.github.com/user" - headers = {"Authorization": f"Bearer {access_token}"} - response = requests.get(github_api_url, headers=headers) - if response.status_code != 200: - logger.error("Failed to fetch user profile from GitHub API") - raise HTTPException(status_code=400, detail="Failed to fetch user profile") - - user_info = response.json() - user_email = user_info.get("email") - if not user_email: - # If the email is not public, make a separate API call to get emails - emails_url = "https://api.github.com/user/emails" - response = requests.get(emails_url, headers=headers) - if response.status_code == 200: - emails = response.json() - user_email = next(email["email"] for email in emails if email["primary"]) - else: - logger.error("Failed to fetch user email from GitHub API") - raise HTTPException(status_code=400, detail="Failed to fetch user email") - - logger.info(f"Retrieved email for user: {current_user} - {user_email}") - - sync_user_input = SyncUserUpdateInput( - credentials=result, - # state={}, - email=user_email, - status=str(SyncsUserStatus.SYNCED), - ) - - sync_user_service.update_sync_user(current_user, state_dict, sync_user_input) - logger.info(f"GitHub sync created successfully for user: {current_user}") - return HTMLResponse(successfullConnectionPage) diff --git a/backend/api/quivr_api/modules/sync/controller/google_sync_routes.py b/backend/api/quivr_api/modules/sync/controller/google_sync_routes.py deleted file mode 100644 index 3e4b5c9a5..000000000 --- a/backend/api/quivr_api/modules/sync/controller/google_sync_routes.py +++ /dev/null @@ -1,170 +0,0 @@ -import json -import os -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException, Request -from fastapi.responses import HTMLResponse -from google_auth_oauthlib.flow import Flow -from googleapiclient.discovery import build - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -from .successfull_connection import successfullConnectionPage - -# Set environment variable for OAuthlib -os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" - -# Initialize logger -logger = get_logger(__name__) - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() - -# Initialize API router -google_sync_router = APIRouter() - -# Constants -SCOPES = [ - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/userinfo.email", - "openid", -] -BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") -BASE_REDIRECT_URI = f"{BACKEND_URL}/sync/google/oauth2callback" - -# Create credentials content from environment variables -CLIENT_SECRETS_FILE_CONTENT = { - "installed": { - "client_id": os.getenv("GOOGLE_CLIENT_ID"), - "project_id": os.getenv("GOOGLE_PROJECT_ID"), - "auth_uri": os.getenv("GOOGLE_AUTH_URI"), - "token_uri": os.getenv("GOOGLE_TOKEN_URI"), - "auth_provider_x509_cert_url": os.getenv("GOOGLE_AUTH_PROVIDER_CERT_URL"), - "client_secret": os.getenv("GOOGLE_CLIENT_SECRET"), - "redirect_uris": os.getenv("GOOGLE_REDIRECT_URI", "http://localhost").split( - "," - ), - "javascript_origins": os.getenv( - "GOOGLE_JAVASCRIPT_ORIGINS", "http://localhost" - ).split(","), - } -} - - -@google_sync_router.post( - "/sync/google/authorize", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -def authorize_google( - request: Request, name: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Authorize Google Drive sync for the current user. - - Args: - request (Request): The request object. - current_user (UserIdentity): The current authenticated user. - - Returns: - dict: A dictionary containing the authorization URL. - """ - logger.debug( - f"Authorizing Google Drive sync for user: {current_user.id}, name : {name}" - ) - redirect_uri = BASE_REDIRECT_URI - flow = Flow.from_client_config( - CLIENT_SECRETS_FILE_CONTENT, - scopes=SCOPES, - redirect_uri=redirect_uri, - ) - state = f"user_id={current_user.id}, name={name}" - authorization_url, state = flow.authorization_url( - access_type="offline", - include_granted_scopes="true", - state=state, - prompt="consent", - ) - logger.info( - f"Generated authorization URL: {authorization_url} for user: {current_user.id}" - ) - sync_user_input = SyncsUserInput( - name=name, - user_id=str(current_user.id), - provider="Google", - credentials={}, - state={"state": state}, - additional_data={}, - status=str(SyncsUserStatus.SYNCED), - ) - sync_user_service.create_sync_user(sync_user_input) - return {"authorization_url": authorization_url} - - -@google_sync_router.get("/sync/google/oauth2callback", tags=["Sync"]) -def oauth2callback_google(request: Request): - """ - Handle OAuth2 callback from Google. - - Args: - request (Request): The request object. - - Returns: - dict: A dictionary containing a success message. - """ - state = request.query_params.get("state") - state_dict = {"state": state} - logger.info(f"State: {state}") - state_split = state.split(",") - current_user = UUID(state_split[0].split("=")[1]) if state else None - assert current_user, f"oauth2callback_googl empty current_user in {request}" - logger.debug( - f"Handling OAuth2 callback for user: {current_user} with state: {state}" - ) - sync_user_state = sync_user_service.get_sync_user_by_state(state_dict) - logger.info(f"Retrieved sync user state: {sync_user_state}") - - if not sync_user_state or state_dict != sync_user_state.state: - logger.error("Invalid state parameter") - raise HTTPException(status_code=400, detail="Invalid state parameter") - if sync_user_state.user_id != current_user: - logger.error("Invalid user") - logger.info(f"Invalid user: {current_user}") - raise HTTPException(status_code=400, detail="Invalid user") - - redirect_uri = f"{BASE_REDIRECT_URI}" - flow = Flow.from_client_config( - CLIENT_SECRETS_FILE_CONTENT, - scopes=SCOPES, - state=state, - redirect_uri=redirect_uri, - ) - flow.fetch_token(authorization_response=str(request.url)) - creds = flow.credentials - logger.info(f"Fetched OAuth2 token for user: {current_user}") - - # Use the credentials to get the user's email - service = build("oauth2", "v2", credentials=creds) - user_info = service.userinfo().get().execute() - user_email = user_info.get("email") - logger.info(f"Retrieved email for user: {current_user} - {user_email}") - - sync_user_input = SyncUserUpdateInput( - credentials=json.loads(creds.to_json()), - # state={}, - email=user_email, - status=str(SyncsUserStatus.SYNCED), - ) - sync_user_service.update_sync_user(current_user, state_dict, sync_user_input) - logger.info(f"Google Drive sync created successfully for user: {current_user}") - return HTMLResponse(successfullConnectionPage) diff --git a/backend/api/quivr_api/modules/sync/controller/notion_sync_routes.py b/backend/api/quivr_api/modules/sync/controller/notion_sync_routes.py deleted file mode 100644 index 4c450ecb1..000000000 --- a/backend/api/quivr_api/modules/sync/controller/notion_sync_routes.py +++ /dev/null @@ -1,172 +0,0 @@ -import base64 -import os -from uuid import UUID - -import requests -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request -from fastapi.responses import HTMLResponse -from notion_client import Client - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -from .successfull_connection import successfullConnectionPage - -NOTION_CLIENT_ID = os.getenv("NOTION_CLIENT_ID") -NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET") -NOTION_AUTH_URL = os.getenv("NOTION_AUTH_URL") -BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") -BASE_REDIRECT_URI = f"{BACKEND_URL}/sync/notion/oauth2callback" -SCOPE = "users.read,databases.read,databases.write,blocks.read,blocks.write" - - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() - -logger = get_logger(__name__) - -# Initialize API router -notion_sync_router = APIRouter() - - -@notion_sync_router.post( - "/sync/notion/authorize", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -def authorize_notion( - request: Request, name: str, current_user: UserIdentity = Depends(get_current_user) -): - """ - Authorize Notion sync for the current user. - - Args: - request (Request): The request object. - current_user (UserIdentity): The current authenticated user. - - Returns: - dict: A dictionary containing the authorization URL. - """ - logger.debug(f"Authorizing Notion sync for user: {current_user.id}, name : {name}") - state: str = f"user_id={current_user.id}, name={name}" - authorize_url = str(NOTION_AUTH_URL) + f"&state={state}" - - logger.info( - f"Generated authorization URL: {authorize_url} for user: {current_user.id}" - ) - sync_user_input = SyncsUserInput( - name=name, - user_id=str(current_user.id), - provider="Notion", - credentials={}, - state={"state": state}, - status=str(SyncsUserStatus.SYNCING), - ) - sync_user_service.create_sync_user(sync_user_input) - return {"authorization_url": authorize_url} - - -@notion_sync_router.get("/sync/notion/oauth2callback", tags=["Sync"]) -def oauth2callback_notion(request: Request, background_tasks: BackgroundTasks): - """ - Handle OAuth2 callback from Notion. - - Args: - request (Request): The request object. - - Returns: - dict: A dictionary containing a success message. - """ - code = request.query_params.get("code") - state = request.query_params.get("state") - if not state: - raise HTTPException(status_code=400, detail="Invalid state parameter") - - state_dict = {"state": state} - state_split = state.split(",") # type: ignore - current_user = UUID(state_split[0].split("=")[1]) if state else None - assert current_user, "Oauth callback user is None" - logger.debug( - f"Handling OAuth2 callback for user: {current_user} with state: {state} and state_dict: {state_dict}" - ) - sync_user_state = sync_user_service.get_sync_user_by_state(state_dict) - - if not sync_user_state or state_dict != sync_user_state.state: - logger.error(f"Invalid state parameter for {sync_user_state}") - raise HTTPException(status_code=400, detail="Invalid state parameter") - else: - logger.info( - f"Current user: {current_user}, sync user state: {sync_user_state.state}" - ) - - if sync_user_state.user_id != current_user: - raise HTTPException(status_code=400, detail="Invalid user") - - try: - token_url = "https://api.notion.com/v1/oauth/token" - client_credentials = f"{NOTION_CLIENT_ID}:{NOTION_CLIENT_SECRET}" - encoded_credentials = base64.b64encode(client_credentials.encode()).decode() - - headers = { - "Authorization": f"Basic {encoded_credentials}", - "Content-Type": "application/json", - } - - token_data = { - "grant_type": "authorization_code", - "code": code, - "redirect_uri": BASE_REDIRECT_URI, - } - logger.debug(f"Requesting token with data: {token_data}") - - response = requests.post(token_url, headers=headers, json=token_data) - oauth_result = response.json() - access_token = oauth_result["access_token"] - - notion = Client(auth=access_token) - - # Get account information - user_info = notion.users.me() - - owner_info = user_info["bot"]["owner"]["user"] # type: ignore - user_email = owner_info["person"]["email"] - account_id = owner_info["id"] - - result: dict[str, str] = { - "access_token": access_token, - "refresh_token": oauth_result.get("refresh_token", ""), - "account_id": account_id, - "expires_in": oauth_result.get("expires_in", ""), - } - - sync_user_input = SyncUserUpdateInput( - credentials=result, - # state={}, - email=user_email, - status=str(SyncsUserStatus.SYNCING), - ) - sync_user_service.update_sync_user(current_user, state_dict, sync_user_input) - logger.info(f"Notion sync created successfully for user: {current_user}") - # launch celery task to sync notion data - celery.send_task( - "fetch_and_store_notion_files_task", - kwargs={ - "access_token": access_token, - "user_id": current_user, - "sync_user_id": sync_user_state.id, - }, - ) - return HTMLResponse(successfullConnectionPage) - - except Exception as e: - logger.error(f"Error: {e}") - raise HTTPException(status_code=400, detail="Invalid user") diff --git a/backend/api/quivr_api/modules/sync/controller/successfull_connection.py b/backend/api/quivr_api/modules/sync/controller/successfull_connection.py deleted file mode 100644 index 0e9f00852..000000000 --- a/backend/api/quivr_api/modules/sync/controller/successfull_connection.py +++ /dev/null @@ -1,53 +0,0 @@ -successfullConnectionPage = """ - - - - - - - -
- -
Connection successful
- -
- - -""" diff --git a/backend/api/quivr_api/modules/sync/controller/sync_routes.py b/backend/api/quivr_api/modules/sync/controller/sync_routes.py deleted file mode 100644 index 3adcbe41b..000000000 --- a/backend/api/quivr_api/modules/sync/controller/sync_routes.py +++ /dev/null @@ -1,411 +0,0 @@ -import os -import uuid -from typing import Annotated, List - -from fastapi import APIRouter, Depends, HTTPException, status - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.notification.dto.inputs import CreateNotification -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.controller.azure_sync_routes import azure_sync_router -from quivr_api.modules.sync.controller.dropbox_sync_routes import dropbox_sync_router -from quivr_api.modules.sync.controller.github_sync_routes import github_sync_router -from quivr_api.modules.sync.controller.google_sync_routes import google_sync_router -from quivr_api.modules.sync.controller.notion_sync_routes import notion_sync_router -from quivr_api.modules.sync.dto import SyncsDescription -from quivr_api.modules.sync.dto.inputs import SyncsActiveInput, SyncsActiveUpdateInput -from quivr_api.modules.sync.dto.outputs import AuthMethodEnum -from quivr_api.modules.sync.entity.sync_models import SyncsActive -from quivr_api.modules.sync.service.sync_notion import SyncNotionService -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.user.entity.user_identity import UserIdentity - -notification_service = NotificationService() - -# Set environment variable for OAuthlib -os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" - -# Initialize logger -logger = get_logger(__name__) - -# Initialize sync service -sync_service = SyncService() -sync_user_service = SyncUserService() -NotionServiceDep = Annotated[SyncNotionService, Depends(get_service(SyncNotionService))] - - -# Initialize API router -sync_router = APIRouter() - -# Add Google routes here -sync_router.include_router(google_sync_router) -sync_router.include_router(azure_sync_router) -sync_router.include_router(github_sync_router) -sync_router.include_router(dropbox_sync_router) -sync_router.include_router(notion_sync_router) - - -# Google sync description -google_sync = SyncsDescription( - name="Google", - description="Sync your Google Drive with Quivr", - auth_method=AuthMethodEnum.URI_WITH_CALLBACK, -) - -azure_sync = SyncsDescription( - name="Azure", - description="Sync your Azure Drive with Quivr", - auth_method=AuthMethodEnum.URI_WITH_CALLBACK, -) - -dropbox_sync = SyncsDescription( - name="DropBox", - description="Sync your DropBox Drive with Quivr", - auth_method=AuthMethodEnum.URI_WITH_CALLBACK, -) - -notion_sync = SyncsDescription( - name="Notion", - description="Sync your Notion with Quivr", - auth_method=AuthMethodEnum.URI_WITH_CALLBACK, -) - -github_sync = SyncsDescription( - name="GitHub", - description="Sync your GitHub Drive with Quivr", - auth_method=AuthMethodEnum.URI_WITH_CALLBACK, -) - - -@sync_router.get( - "/sync/all", - response_model=List[SyncsDescription], - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def get_syncs(current_user: UserIdentity = Depends(get_current_user)): - """ - Get all available sync descriptions. - - Args: - current_user (UserIdentity): The current authenticated user. - - Returns: - List[SyncsDescription]: A list of available sync descriptions. - """ - logger.debug(f"Fetching all sync descriptions for user: {current_user.id}") - return [google_sync, azure_sync, dropbox_sync, notion_sync] - - -@sync_router.get( - "/sync", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def get_user_syncs(current_user: UserIdentity = Depends(get_current_user)): - """ - Get syncs for the current user. - - Args: - current_user (UserIdentity): The current authenticated user. - - Returns: - List: A list of syncs for the user. - """ - logger.debug(f"Fetching user syncs for user: {current_user.id}") - return sync_user_service.get_syncs_user(current_user.id) - - -@sync_router.delete( - "/sync/{sync_id}", - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def delete_user_sync( - sync_id: int, current_user: UserIdentity = Depends(get_current_user) -): - """ - Delete a sync for the current user. - - Args: - sync_id (int): The ID of the sync to delete. - current_user (UserIdentity): The current authenticated user. - - Returns: - None - """ - logger.debug( - f"Deleting user sync for user: {current_user.id} with sync ID: {sync_id}" - ) - sync_user_service.delete_sync_user(sync_id, str(current_user.id)) # type: ignore - return None - - -@sync_router.post( - "/sync/active", - response_model=SyncsActive, - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def create_sync_active( - sync_active_input: SyncsActiveInput, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Create a new active sync for the current user. - - Args: - sync_active_input (SyncsActiveInput): The sync active input data. - current_user (UserIdentity): The current authenticated user. - - Returns: - SyncsActive: The created sync active data. - """ - logger.debug( - f"Creating active sync for user: {current_user.id} with data: {sync_active_input}" - ) - bulk_id = uuid.uuid4() - notification = notification_service.add_notification( - CreateNotification( - user_id=current_user.id, - status=NotificationsStatusEnum.INFO, - title="Synchronization created! ", - description="Your brain is preparing to sync files. This may take a few minutes before proceeding.", - category="generic", - bulk_id=bulk_id, - brain_id=sync_active_input.brain_id, - ) - ) - sync_active_input.notification_id = str(notification.id) - sync_active = sync_service.create_sync_active( - sync_active_input, str(current_user.id) - ) - if not sync_active: - raise HTTPException( - status_code=500, detail=f"Error creating sync active for {current_user}" - ) - - celery.send_task( - "process_sync_task", - kwargs={ - "sync_id": sync_active.id, - "user_id": sync_active.user_id, - "files_ids": sync_active_input.settings.files, - "folder_ids": sync_active_input.settings.folders, - }, - ) - - return sync_active - - -@sync_router.put( - "/sync/active/{sync_id}", - response_model=SyncsActive | None, - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def update_sync_active( - sync_id: int, - sync_active_input: SyncsActiveUpdateInput, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Update an existing active sync for the current user. - - Args: - sync_id (str): The ID of the active sync to update. - sync_active_input (SyncsActiveUpdateInput): The updated sync active input data. - current_user (UserIdentity): The current authenticated user. - - Returns: - SyncsActive: The updated sync active data. - """ - logger.info( - f"Updating active sync for user: {current_user.id} with data: {sync_active_input}" - ) - - details_sync_active = sync_service.get_details_sync_active(sync_id) - - if details_sync_active is None: - raise HTTPException( - status_code=500, - detail="Error updating sync", - ) - - if sync_active_input.settings is None: - return {"message": "No modification to sync active"} - - input_file_ids = ( - sync_active_input.settings.files if sync_active_input.settings.files else [] - ) - input_folder_ids = ( - sync_active_input.settings.folders if sync_active_input.settings.folders else [] - ) - - if (input_file_ids == details_sync_active["settings"]["files"]) and ( - input_folder_ids == details_sync_active["settings"]["folders"] - ): - logger.info({"message": "No modification to sync active"}) - return None - - logger.debug( - f"Updating sync_id {details_sync_active['id']}. Sync prev_settings={details_sync_active['settings'] }, Sync active input={sync_active_input.settings}" - ) - - bulk_id = uuid.uuid4() - sync_active_input.force_sync = True - notification = notification_service.add_notification( - CreateNotification( - user_id=current_user.id, - status=NotificationsStatusEnum.INFO, - title="Sync updated! Synchronization takes a few minutes to complete", - description="Your brain is syncing files. This may take a few minutes before proceeding.", - category="generic", - bulk_id=bulk_id, - brain_id=details_sync_active["brain_id"], # type: ignore - ) - ) - sync_active_input.notification_id = str(notification.id) - sync_active = sync_service.update_sync_active(sync_id, sync_active_input) - if not sync_active: - raise HTTPException( - status_code=500, - detail=f"Error updating sync active for {current_user.id}", - ) - logger.debug( - f"Sending task process_sync_task for sync_id={sync_id}, user_id={current_user.id}" - ) - - added_files_ids = set(input_file_ids).difference( - set(details_sync_active["settings"]["files"]) - ) - added_folder_ids = set(input_folder_ids).difference( - set(details_sync_active["settings"]["folders"]) - ) - if len(added_files_ids) + len(added_folder_ids) > 0: - celery.send_task( - "process_sync_task", - kwargs={ - "sync_id": sync_active.id, - "user_id": sync_active.user_id, - "files_ids": list(added_files_ids), - "folder_ids": list(added_folder_ids), - }, - ) - - else: - return None - - -@sync_router.delete( - "/sync/active/{sync_id}", - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def delete_sync_active( - sync_id: int, current_user: UserIdentity = Depends(get_current_user) -): - """ - Delete an existing active sync for the current user. - - Args: - sync_id (str): The ID of the active sync to delete. - current_user (UserIdentity): The current authenticated user. - - Returns: - None - """ - logger.debug( - f"Deleting active sync for user: {current_user.id} with sync ID: {sync_id}" - ) - - details_sync_active = sync_service.get_details_sync_active(sync_id) - notification_service.add_notification( - CreateNotification( - user_id=current_user.id, - status=NotificationsStatusEnum.SUCCESS, - title="Sync deleted!", - description="Sync deleted!", - category="generic", - bulk_id=uuid.uuid4(), - brain_id=details_sync_active["brain_id"], # type: ignore - ) - ) - sync_service.delete_sync_active(sync_id, str(current_user.id)) # type: ignore - return None - - -@sync_router.get( - "/sync/active", - response_model=List[SyncsActive], - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def get_active_syncs_for_user( - current_user: UserIdentity = Depends(get_current_user), -): - """ - Get all active syncs for the current user. - - Args: - current_user (UserIdentity): The current authenticated user. - - Returns: - List[SyncsActive]: A list of active syncs for the current user. - """ - logger.debug(f"Fetching active syncs for user: {current_user.id}") - return sync_service.get_syncs_active(str(current_user.id)) - - -@sync_router.get( - "/sync/{sync_id}/files", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def get_files_folder_user_sync( - user_sync_id: int, - notion_service: NotionServiceDep, - folder_id: str | None = None, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Get files for an active sync. - - Args: - sync_id (str): The ID of the active sync. - folder_id (str): The ID of the folder to get files from. - current_user (UserIdentity): The current authenticated user. - - Returns: - SyncsActive: The active sync data. - """ - logger.debug( - f"Fetching files for user sync: {user_sync_id} for user: {current_user.id}" - ) - return await sync_user_service.get_files_folder_user_sync( - user_sync_id, current_user.id, folder_id, notion_service=notion_service - ) - - -@sync_router.get( - "/sync/active/interval", - dependencies=[Depends(AuthBearer())], - tags=["Sync"], -) -async def get_syncs_active_in_interval() -> List[SyncsActive]: - """ - Get all active syncs that need to be synced. - - Returns: - List: A list of active syncs that need to be synced. - """ - logger.debug("Fetching active syncs in interval") - return await sync_service.get_syncs_active_in_interval() diff --git a/backend/api/quivr_api/modules/sync/dto/__init__.py b/backend/api/quivr_api/modules/sync/dto/__init__.py deleted file mode 100644 index 986765df6..000000000 --- a/backend/api/quivr_api/modules/sync/dto/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .outputs import SyncsDescription, SyncsUserOutput diff --git a/backend/api/quivr_api/modules/sync/dto/inputs.py b/backend/api/quivr_api/modules/sync/dto/inputs.py deleted file mode 100644 index b192216ac..000000000 --- a/backend/api/quivr_api/modules/sync/dto/inputs.py +++ /dev/null @@ -1,130 +0,0 @@ -import enum -from typing import List, Optional - -from pydantic import BaseModel - - -class SyncsUserStatus(enum.Enum): - """ - Enum for the status of a sync user. - """ - - SYNCED = "SYNCED" - SYNCING = "SYNCING" - ERROR = "ERROR" - REMOVED = "REMOVED" - - def __str__(self): - return self.value - - -class SyncsUserInput(BaseModel): - """ - Input model for creating a new sync user. - - Attributes: - user_id (str): The unique identifier for the user. - name (str): The name of the user. - provider (str): The provider of the sync service (e.g., Google, Azure). - credentials (dict): The credentials required for the sync service. - state (dict): The state information for the sync user. - """ - - user_id: str - name: str - email: str | None = None - provider: str - credentials: dict - state: dict - additional_data: dict = {} - status: str - - -class SyncUserUpdateInput(BaseModel): - """ - Input model for updating an existing sync user. - - Attributes: - credentials (dict): The updated credentials for the sync service. - state (dict): The updated state information for the sync user. - """ - - credentials: dict - state: dict | None = None - email: str - status: str - - -class SyncActiveSettings(BaseModel): - """ - Sync active settings. - - Attributes: - folders (List[str] | None): A list of folder paths to be synced, or None if not applicable. - files (List[str] | None): A list of file paths to be synced, or None if not applicable. - """ - - folders: Optional[List[str]] = None - files: Optional[List[str]] = None - - -class SyncsActiveInput(BaseModel): - """ - Input model for creating a new active sync. - - Attributes: - name (str): The name of the sync. - syncs_user_id (int): The ID of the sync user associated with this sync. - settings (SyncActiveSettings): The settings for the active sync. - """ - - name: str - syncs_user_id: int - settings: SyncActiveSettings - brain_id: str - notification_id: Optional[str] = None - - -class SyncsActiveUpdateInput(BaseModel): - """ - Input model for updating an existing active sync. - - Attributes: - name (str): The updated name of the sync. - sync_interval_minutes (int): The updated sync interval in minutes. - settings (dict): The updated settings for the active sync. - """ - - name: Optional[str] = None - settings: Optional[SyncActiveSettings] = None - last_synced: Optional[str] = None - force_sync: Optional[bool] = False - notification_id: Optional[str] = None - - -class SyncFileInput(BaseModel): - """ - Input model for creating a new sync file. - - Attributes: - path (str): The path of the file. - syncs_active_id (int): The ID of the active sync associated with this file. - """ - - path: str - syncs_active_id: int - last_modified: str - brain_id: str - supported: Optional[bool] = True - - -class SyncFileUpdateInput(BaseModel): - """ - Input model for updating an existing sync file. - - Attributes: - last_modified (datetime.datetime): The updated last modified date and time. - """ - - last_modified: Optional[str] = None - supported: Optional[bool] = None diff --git a/backend/api/quivr_api/modules/sync/dto/outputs.py b/backend/api/quivr_api/modules/sync/dto/outputs.py deleted file mode 100644 index 498f66177..000000000 --- a/backend/api/quivr_api/modules/sync/dto/outputs.py +++ /dev/null @@ -1,20 +0,0 @@ -from enum import Enum - -from pydantic import BaseModel - - -class AuthMethodEnum(str, Enum): - URI_WITH_CALLBACK = "uri_with_callback" - - -class SyncsDescription(BaseModel): - name: str - description: str - auth_method: AuthMethodEnum - - -class SyncsUserOutput(BaseModel): - user_id: str - provider: str - state: dict - credentials: dict diff --git a/backend/api/quivr_api/modules/sync/entity/__init__.py b/backend/api/quivr_api/modules/sync/entity/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/entity/notion_page.py b/backend/api/quivr_api/modules/sync/entity/notion_page.py deleted file mode 100644 index 8facf5d1a..000000000 --- a/backend/api/quivr_api/modules/sync/entity/notion_page.py +++ /dev/null @@ -1,137 +0,0 @@ -from datetime import datetime -from typing import Any, List, Literal, Union -from uuid import UUID - -from pydantic import BaseModel, ConfigDict, Field, field_validator - -from quivr_api.modules.sync.entity.sync_models import NotionSyncFile - - -class TextContent(BaseModel): - content: str | None - link: str | None - - -class TitleItem(BaseModel): - type: Literal["text"] - text: TextContent - annotations: dict[str, Any] - plain_text: str - - -class Title(BaseModel): - id: str - type: Literal["title"] - title: List[TitleItem] - - -class StatusData(BaseModel): - id: UUID - name: str - color: str - - -class Status(BaseModel): - id: str - type: Literal["status"] - status: StatusData - - -class Icon(BaseModel): - type: Literal["emoji"] - emoji: str - - -class ExternalCoverData(BaseModel): - url: str - - -class Cover(BaseModel): - type: Literal["external"] - external: ExternalCoverData - - -class PageParent(BaseModel): - type: Literal["page_id"] - page_id: UUID - - -class BlockParent(BaseModel): - type: Literal["block_id"] - block_id: UUID - - -class DatabaseParent(BaseModel): - type: Literal["database_id"] - database_id: UUID - - -class WorkspaceParent(BaseModel): - type: Literal["workspace"] - workspace: bool = True - - @field_validator("workspace") - @classmethod - def workspace_always_true(cls, w: bool) -> bool: - assert w, "workspace value always True" - return w - - -class PageProps(BaseModel): - title: Title | None = None - - -class NotionPage(BaseModel): - model_config = ConfigDict(extra="allow") - - id: UUID - created_time: datetime - last_edited_time: datetime - url: str - archived: bool - in_trash: bool | None - public_url: str | None - parent: Union[PageParent, DatabaseParent, WorkspaceParent, BlockParent] = Field( - discriminator="type" - ) - cover: Cover | None - icon: Icon | None - properties: PageProps - sync_user_id: UUID | None = Field(default=None, foreign_key="syncs_user.id") # type: ignore - - # TODO: Fix UUID in table NOTION - def _get_parent_id(self) -> UUID | None: - match self.parent: - case PageParent(page_id=page_id): - return page_id - case DatabaseParent(): - return None - case WorkspaceParent(): - return None - case BlockParent(): - return None - - def to_syncfile(self, user_id: UUID, sync_user_id: int) -> NotionSyncFile: - name = ( - self.properties.title.title[0].text.content if self.properties.title else "" - ) - return NotionSyncFile( - notion_id=self.id, # TODO: store as UUID - parent_id=self._get_parent_id(), - name=f"{name}.md", - icon=self.icon.emoji if self.icon else "", - mime_type="md", - web_view_link=self.url, - is_folder=True, - last_modified=self.last_edited_time, - type="page", - user_id=user_id, - sync_user_id=sync_user_id, - ) - - -class NotionSearchResult(BaseModel): - model_config = ConfigDict(extra="allow") - results: list[NotionPage] - has_more: bool - next_cursor: str | None diff --git a/backend/api/quivr_api/modules/sync/entity/sync_models.py b/backend/api/quivr_api/modules/sync/entity/sync_models.py deleted file mode 100644 index 0b22cba38..000000000 --- a/backend/api/quivr_api/modules/sync/entity/sync_models.py +++ /dev/null @@ -1,122 +0,0 @@ -import hashlib -import io -from dataclasses import dataclass -from datetime import datetime -from typing import Optional -from uuid import UUID - -from pydantic import BaseModel -from sqlmodel import TIMESTAMP, Column, Field, Relationship, SQLModel, text -from sqlmodel import UUID as PGUUID - -from quivr_api.modules.user.entity.user_identity import User - - -@dataclass -class DownloadedSyncFile: - file_name: str - extension: str - file_data: io.BufferedReader - - def file_sha1(self) -> str: - m = hashlib.sha1() - self.file_data.seek(0) - data = self.file_data.read() - m.update(data) - self.file_data.seek(0) - return m.hexdigest() - - -class DBSyncFile(BaseModel): - id: int - path: str - syncs_active_id: int - last_modified: str - brain_id: str - supported: bool - - -class SyncFile(BaseModel): - id: str - name: str - is_folder: bool - last_modified: str - mime_type: str - web_view_link: str - size: Optional[int] = None - notification_id: UUID | None = None - icon: Optional[str] = None - parent_id: Optional[str] = None - type: Optional[str] = None - - -class SyncsUser(BaseModel): - id: int - user_id: UUID - name: str - email: str | None = None - provider: str - credentials: dict - state: dict - additional_data: dict - status: Optional[str] = None - - -class SyncsActive(BaseModel): - id: int - name: str - syncs_user_id: int - user_id: UUID - settings: dict - last_synced: str - sync_interval_minutes: int - brain_id: UUID - syncs_user: Optional[SyncsUser] = None - notification_id: Optional[str] = None - - -# TODO: all of this should be rewritten -class SyncsActiveDetails(BaseModel): - pass - - -class NotionSyncFile(SQLModel, table=True): - """ - Represents a file synchronized with Notion. - """ - - __tablename__ = "notion_sync" # type: ignore - - id: Optional[UUID] = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - notion_id: UUID = Field(unique=True, description="The ID of the file in Notion") - parent_id: UUID | None = Field( - default=None, description="The ID of the parent file or directory" - ) - name: str = Field(default=None, description="The name of the file") - icon: Optional[str] = Field(description="The icon associated with the file") - mime_type: str = Field(default=None, description="The MIME type of the file") - web_view_link: str = Field(description="The web view link for the file") - is_folder: bool = Field(description="Indicates if the file is a folder") - last_modified: datetime = Field( - sa_column=Column(TIMESTAMP(timezone=True)), - description="The last modified timestamp of the file", - ) - type: Optional[str] = Field( - default=None, description="The type/category of the file" - ) - user_id: UUID = Field( - foreign_key="users.id", - description="The ID of the user who owns the file", - ) - user: User = Relationship(back_populates="notion_syncs") - sync_user_id: int = Field( - # foreign_key="syncs_user.id", - description="The ID of the sync user associated with the file", - ) diff --git a/backend/api/quivr_api/modules/sync/repository/__init__.py b/backend/api/quivr_api/modules/sync/repository/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/repository/sync_files.py b/backend/api/quivr_api/modules/sync/repository/sync_files.py deleted file mode 100644 index e814192a7..000000000 --- a/backend/api/quivr_api/modules/sync/repository/sync_files.py +++ /dev/null @@ -1,129 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.sync.dto.inputs import ( - SyncFileInput, - SyncFileUpdateInput, -) -from quivr_api.modules.sync.entity.sync_models import DBSyncFile, SyncFile, SyncsActive -from quivr_api.modules.sync.repository.sync_interfaces import SyncFileInterface - -logger = get_logger(__name__) - - -class SyncFilesRepository(SyncFileInterface): - def __init__(self): - """ - Initialize the SyncFiles class with a Supabase client. - """ - supabase_client = get_supabase_client() - self.db = supabase_client # type: ignore - logger.debug("Supabase client initialized") - - def create_sync_file(self, sync_file_input: SyncFileInput) -> DBSyncFile | None: - """ - Create a new sync file in the database. - - Args: - sync_file_input (SyncFileInput): The input data for creating a sync file. - - Returns: - SyncsFiles: The created sync file data. - """ - logger.info("Creating sync file with input: %s", sync_file_input) - response = ( - self.db.from_("syncs_files") - .insert( - { - "path": sync_file_input.path, - "syncs_active_id": sync_file_input.syncs_active_id, - "last_modified": sync_file_input.last_modified, - "brain_id": sync_file_input.brain_id, - } - ) - .execute() - ) - if response.data: - logger.info("Sync file created successfully: %s", response.data[0]) - return DBSyncFile(**response.data[0]) - logger.warning("Failed to create sync file") - - def get_sync_files(self, sync_active_id: int) -> list[DBSyncFile]: - """ - Retrieve sync files from the database. - - Args: - sync_active_id (int): The ID of the active sync. - - Returns: - list[SyncsFiles]: A list of sync files matching the criteria. - """ - logger.info("Retrieving sync files for sync_active_id: %s", sync_active_id) - response = ( - self.db.from_("syncs_files") - .select("*") - .eq("syncs_active_id", sync_active_id) - .execute() - ) - if response.data: - # logger.info("Sync files retrieved successfully: %s", response.data) - return [DBSyncFile(**file) for file in response.data] - logger.warning("No sync files found for sync_active_id: %s", sync_active_id) - return [] - - def update_sync_file(self, sync_file_id: int, sync_file_input: SyncFileUpdateInput): - """ - Update a sync file in the database. - - Args: - sync_file_id (int): The ID of the sync file. - sync_file_input (SyncFileUpdateInput): The input data for updating the sync file. - """ - logger.info( - "Updating sync file with sync_file_id: %s, input: %s", - sync_file_id, - sync_file_input, - ) - self.db.from_("syncs_files").update( - sync_file_input.model_dump(exclude_unset=True) - ).eq("id", sync_file_id).execute() - logger.info("Sync file updated successfully") - - def update_or_create_sync_file( - self, - file: SyncFile, - sync_active: SyncsActive, - previous_file: DBSyncFile | None, - supported: bool, - ) -> DBSyncFile | None: - if previous_file: - logger.debug(f"Upserting file {previous_file} in database.") - sync_file = self.update_sync_file( - previous_file.id, - SyncFileUpdateInput( - last_modified=file.last_modified, - supported=previous_file.supported or supported, - ), - ) - else: - logger.debug("Creating new file in database.") - sync_file = self.create_sync_file( - SyncFileInput( - path=file.name, - syncs_active_id=sync_active.id, - last_modified=file.last_modified, - brain_id=str(sync_active.brain_id), - supported=supported, - ) - ) - return sync_file - - def delete_sync_file(self, sync_file_id: int): - """ - Delete a sync file from the database. - - Args: - sync_file_id (int): The ID of the sync file. - """ - logger.info("Deleting sync file with sync_file_id: %s", sync_file_id) - self.db.from_("syncs_files").delete().eq("id", sync_file_id).execute() - logger.info("Sync file deleted successfully") diff --git a/backend/api/quivr_api/modules/sync/repository/sync_interfaces.py b/backend/api/quivr_api/modules/sync/repository/sync_interfaces.py deleted file mode 100644 index dc6a9add6..000000000 --- a/backend/api/quivr_api/modules/sync/repository/sync_interfaces.py +++ /dev/null @@ -1,123 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, List, Literal -from uuid import UUID - -from quivr_api.modules.sync.dto.inputs import ( - SyncFileInput, - SyncFileUpdateInput, - SyncsActiveInput, - SyncsActiveUpdateInput, - SyncsUserInput, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.entity.sync_models import ( - DBSyncFile, - SyncFile, - SyncsActive, -) - - -class SyncUserInterface(ABC): - @abstractmethod - def create_sync_user( - self, - sync_user_input: SyncsUserInput, - ): - pass - - @abstractmethod - def get_syncs_user(self, user_id: str, sync_user_id: int | None = None): - pass - - @abstractmethod - def get_sync_user_by_id(self, sync_id: int): - pass - - @abstractmethod - def delete_sync_user(self, sync_user_id: int, user_id: UUID | str): - pass - - @abstractmethod - def get_sync_user_by_state(self, state: dict): - pass - - @abstractmethod - def update_sync_user( - self, sync_user_id: int, state: dict, sync_user_input: SyncUserUpdateInput - ): - pass - - @abstractmethod - async def get_files_folder_user_sync( - self, - sync_active_id: int, - user_id: str, - notion_service: Any = None, - folder_id: int | str | None = None, - recursive: bool = False, - ) -> None | dict[str, List[SyncFile]] | Literal["No sync found"]: - pass - - @abstractmethod - def get_all_notion_user_syncs(self): - pass - - -class SyncInterface(ABC): - @abstractmethod - def create_sync_active( - self, - sync_active_input: SyncsActiveInput, - user_id: str, - ) -> SyncsActive | None: - pass - - @abstractmethod - def get_syncs_active(self, user_id: UUID | str) -> List[SyncsActive]: - pass - - @abstractmethod - def update_sync_active( - self, sync_id: UUID | int, sync_active_input: SyncsActiveUpdateInput - ): - pass - - @abstractmethod - def delete_sync_active(self, sync_active_id: int, user_id: str): - pass - - @abstractmethod - def get_details_sync_active(self, sync_active_id: int): - pass - - @abstractmethod - async def get_syncs_active_in_interval(self) -> List[SyncsActive]: - pass - - -class SyncFileInterface(ABC): - @abstractmethod - def create_sync_file(self, sync_file_input: SyncFileInput) -> DBSyncFile: - pass - - @abstractmethod - def get_sync_files(self, sync_active_id: int) -> list[DBSyncFile]: - pass - - @abstractmethod - def update_sync_file(self, sync_file_id: int, sync_file_input: SyncFileUpdateInput): - pass - - @abstractmethod - def delete_sync_file(self, sync_file_id: int): - pass - - @abstractmethod - def update_or_create_sync_file( - self, - file: SyncFile, - sync_active: SyncsActive, - previous_file: DBSyncFile | None, - supported: bool, - ) -> DBSyncFile | None: - pass diff --git a/backend/api/quivr_api/modules/sync/repository/sync_repository.py b/backend/api/quivr_api/modules/sync/repository/sync_repository.py deleted file mode 100644 index e669d582e..000000000 --- a/backend/api/quivr_api/modules/sync/repository/sync_repository.py +++ /dev/null @@ -1,327 +0,0 @@ -from datetime import datetime, timedelta -from typing import List, Sequence -from uuid import UUID - -from sqlalchemy import or_ -from sqlalchemy.exc import IntegrityError -from sqlmodel import col, select -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseRepository, get_supabase_client -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.dto.inputs import SyncsActiveInput, SyncsActiveUpdateInput -from quivr_api.modules.sync.entity.sync_models import NotionSyncFile, SyncsActive -from quivr_api.modules.sync.repository.sync_interfaces import SyncInterface - -notification_service = NotificationService() - -logger = get_logger(__name__) - - -class Sync(SyncInterface): - def __init__(self): - """ - Initialize the Sync class with a Supabase client. - """ - supabase_client = get_supabase_client() - self.db = supabase_client # type: ignore - logger.debug("Supabase client initialized") - - def create_sync_active( - self, sync_active_input: SyncsActiveInput, user_id: str - ) -> SyncsActive | None: - """ - Create a new active sync in the database. - - Args: - sync_active_input (SyncsActiveInput): The input data for creating an active sync. - user_id (str): The user ID associated with the active sync. - - Returns: - SyncsActive or None: The created active sync data or None if creation failed. - """ - logger.info( - "Creating active sync for user_id: %s with input: %s", - user_id, - sync_active_input, - ) - sync_active_input_dict = sync_active_input.model_dump() - sync_active_input_dict["user_id"] = user_id - response = ( - self.db.from_("syncs_active").insert(sync_active_input_dict).execute() - ) - if response.data: - logger.info("Active sync created successfully: %s", response.data[0]) - return SyncsActive(**response.data[0]) - - logger.error("Failed to create active sync for user_id: %s", user_id) - - def get_syncs_active(self, user_id: UUID | str) -> List[SyncsActive]: - """ - Retrieve active syncs from the database. - - Args: - user_id (str): The user ID to filter active syncs. - - Returns: - List[SyncsActive]: A list of active syncs matching the criteria. - """ - logger.info("Retrieving active syncs for user_id: %s", user_id) - response = ( - self.db.from_("syncs_active") - .select("*, syncs_user(*)") - .eq("user_id", user_id) - .execute() - ) - if response.data: - logger.info("Active syncs retrieved successfully: %s", response.data) - return [SyncsActive(**sync) for sync in response.data] - logger.warning("No active syncs found for user_id: %s", user_id) - return [] - - def update_sync_active( - self, sync_id: int | str, sync_active_input: SyncsActiveUpdateInput - ) -> SyncsActive | None: - """ - Update an active sync in the database. - - Args: - sync_id (int): The ID of the active sync. - sync_active_input (SyncsActiveUpdateInput): The input data for updating the active sync. - - Returns: - dict or None: The updated active sync data or None if update failed. - """ - logger.info( - "Updating active sync with sync_id: %s, input: %s", - sync_id, - sync_active_input, - ) - - response = ( - self.db.from_("syncs_active") - .update(sync_active_input.model_dump(exclude_unset=True)) - .eq("id", sync_id) - .execute() - ) - - if response.data: - logger.info("Active sync updated successfully: %s", response.data[0]) - return SyncsActive.model_validate(response.data[0]) - - logger.error("Failed to update active sync with sync_id: %s", sync_id) - - def delete_sync_active(self, sync_active_id: int, user_id: UUID): - """ - Delete an active sync from the database. - - Args: - sync_active_id (int): The ID of the active sync. - user_id (str): The user ID associated with the active sync. - - Returns: - dict or None: The deleted active sync data or None if deletion failed. - """ - logger.info( - "Deleting active sync with sync_active_id: %s, user_id: %s", - sync_active_id, - user_id, - ) - response = ( - self.db.from_("syncs_active") - .delete() - .eq("id", sync_active_id) - .eq("user_id", str(user_id)) - .execute() - ) - if response.data: - logger.info("Active sync deleted successfully: %s", response.data[0]) - return response.data[0] - logger.warning( - "Failed to delete active sync with sync_active_id: %s, user_id: %s", - sync_active_id, - user_id, - ) - return None - - def get_details_sync_active(self, sync_active_id: int): - """ - Retrieve details of an active sync, including associated sync user data. - - Args: - sync_active_id (int): The ID of the active sync. - - Returns: - dict or None: The detailed active sync data or None if not found. - """ - logger.info( - "Retrieving details for active sync with sync_active_id: %s", sync_active_id - ) - response = ( - self.db.table("syncs_active") - .select("*, syncs_user(provider, credentials)") - .eq("id", sync_active_id) - .execute() - ) - if response.data: - logger.info( - "Details for active sync retrieved successfully: %s", response.data[0] - ) - return response.data[0] - logger.warning( - "No details found for active sync with sync_active_id: %s", sync_active_id - ) - return None - - async def get_syncs_active_in_interval(self) -> List[SyncsActive]: - """ - Retrieve active syncs that are due for synchronization based on their interval. - - Returns: - list: A list of active syncs that are due for synchronization. - """ - logger.info("Retrieving active syncs due for synchronization") - - current_time = datetime.now() - - # The Query filters the active syncs based on the sync_interval_minutes field and last_synced timestamp - response = ( - self.db.table("syncs_active") - .select("*") - .lt("last_synced", (current_time - timedelta(minutes=360)).isoformat()) - .execute() - ) - - force_sync = ( - self.db.table("syncs_active").select("*").eq("force_sync", True).execute() - ) - merge_data = response.data + force_sync.data - if merge_data: - logger.info("Active syncs retrieved successfully: %s", merge_data) - return [SyncsActive(**sync) for sync in merge_data] - logger.info("No active syncs found due for synchronization") - return [] - - -class NotionRepository(BaseRepository): - def __init__(self, session: AsyncSession): - super().__init__(session) - self.session = session - self.db = get_supabase_client() - - async def get_user_notion_files( - self, user_id: UUID, sync_user_id: int - ) -> Sequence[NotionSyncFile]: - query = select(NotionSyncFile).where( - NotionSyncFile.user_id == user_id - and NotionSyncFile.sync_user_id == sync_user_id - ) - response = await self.session.exec(query) - return response.all() - - async def create_notion_files( - self, notion_files: List[NotionSyncFile] - ) -> List[NotionSyncFile]: - try: - self.session.add_all(notion_files) - await self.session.commit() - except IntegrityError: - await self.session.rollback() - raise Exception("Integrity error while creating notion files.") - except Exception as e: - await self.session.rollback() - raise e - - return notion_files - - async def update_notion_file(self, updated_notion_file: NotionSyncFile) -> bool: - try: - is_update = False - query = select(NotionSyncFile).where( - NotionSyncFile.notion_id == updated_notion_file.notion_id - ) - result = await self.session.exec(query) - existing_page = result.one_or_none() - - if existing_page: - # Update existing page - existing_page.name = updated_notion_file.name - existing_page.last_modified = updated_notion_file.last_modified - self.session.add(existing_page) - is_update = True - else: - # Add new page - self.session.add(updated_notion_file) - - await self.session.commit() - - # Refresh the object that's actually in the session - refreshed_file = existing_page if existing_page else updated_notion_file - await self.session.refresh(refreshed_file) - - logger.info(f"Updated notion file in notion repo: {refreshed_file}") - return is_update - - except IntegrityError as ie: - logger.error(f"IntegrityError occurred: {ie}") - await self.session.rollback() - raise Exception("Integrity error while updating notion file.") - except Exception as e: - logger.error(f"Exception occurred: {e}") - await self.session.rollback() - raise - - async def get_notion_files_by_ids(self, ids: List[str]) -> Sequence[NotionSyncFile]: - query = select(NotionSyncFile).where(NotionSyncFile.notion_id.in_(ids)) # type: ignore - response = await self.session.exec(query) - return response.all() - - async def get_notion_files_by_parent_id( - self, parent_id: str | None, sync_user_id: int - ) -> Sequence[NotionSyncFile]: - query = ( - select(NotionSyncFile) - .where(NotionSyncFile.parent_id == parent_id) - .where(NotionSyncFile.sync_user_id == sync_user_id) - ) - response = await self.session.exec(query) - return response.all() - - async def get_all_notion_files(self) -> Sequence[NotionSyncFile]: - query = select(NotionSyncFile) - response = await self.session.exec(query) - return response.all() - - async def is_folder_page(self, page_id: str) -> bool: - query = select(NotionSyncFile).where(NotionSyncFile.parent_id == page_id) - response = await self.session.exec(query) - return response.first() is not None - - async def delete_notion_page(self, notion_id: UUID): - query = select(NotionSyncFile).where(NotionSyncFile.notion_id == notion_id) - response = await self.session.exec(query) - notion_file = response.first() - if notion_file: - await self.session.delete(notion_file) - await self.session.commit() - return notion_file - return None - - async def delete_notion_pages(self, notion_ids: List[UUID]): - query = select(NotionSyncFile).where( - or_( - col(NotionSyncFile.notion_id).in_(notion_ids), - col(NotionSyncFile.parent_id).in_(notion_ids), - ) - ) - response = await self.session.exec(query) - notion_files = response.all() - if notion_files: - for notion_file in notion_files: - await self.session.delete(notion_file) - await self.session.commit() - return notion_files - return None diff --git a/backend/api/quivr_api/modules/sync/repository/sync_user.py b/backend/api/quivr_api/modules/sync/repository/sync_user.py deleted file mode 100644 index 09ff5007d..000000000 --- a/backend/api/quivr_api/modules/sync/repository/sync_user.py +++ /dev/null @@ -1,319 +0,0 @@ -import json -from typing import List, Literal -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.sync.dto.inputs import ( - SyncsUserInput, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.entity.sync_models import SyncFile, SyncsUser -from quivr_api.modules.sync.service.sync_notion import SyncNotionService -from quivr_api.modules.sync.utils.sync import ( - AzureDriveSync, - BaseSync, - DropboxSync, - GitHubSync, - GoogleDriveSync, - NotionSync, -) - -logger = get_logger(__name__) - - -class SyncUserRepository: - def __init__(self): - """ - Initialize the Sync class with a Supabase client. - """ - supabase_client = get_supabase_client() - self.db = supabase_client # type: ignore - logger.debug("Supabase client initialized") - - def create_sync_user( - self, - sync_user_input: SyncsUserInput, - ): - """ - Create a new sync user in the database. - - Args: - sync_user_input (SyncsUserInput): The input data for creating a sync user. - - Returns: - dict or None: The created sync user data or None if creation failed. - """ - logger.info("Creating sync user with input: %s", sync_user_input) - response = ( - self.db.from_("syncs_user") - .insert(sync_user_input.model_dump(exclude_none=True, exclude_unset=True)) - .execute() - ) - - if response.data: - logger.info("Sync user created successfully: %s", response.data[0]) - return response.data[0] - logger.warning("Failed to create sync user") - - def get_sync_user_by_id(self, sync_id: int) -> SyncsUser | None: - """ - Retrieve sync users from the database. - """ - response = self.db.from_("syncs_user").select("*").eq("id", sync_id).execute() - if response.data: - logger.info("Sync user found: %s", response.data[0]) - return SyncsUser.model_validate(response.data[0]) - logger.error("No sync user found for sync_id: %s", sync_id) - - def clean_notion_user_syncs(self): - """ - Clean all Removed Notion sync users from the database. - """ - logger.info("Cleaning all Removed Notion sync users") - self.db.from_("syncs_user").delete().eq("provider", "Notion").eq( - "status", "REMOVED" - ).execute() - logger.info("Removed Notion sync users cleaned successfully") - - def get_syncs_user(self, user_id: UUID, sync_user_id: int | None = None): - """ - Retrieve sync users from the database. - - Args: - user_id (str): The user ID to filter sync users. - sync_user_id (int, optional): The sync user ID to filter sync users. Defaults to None. - - Returns: - list: A list of sync users matching the criteria. - """ - logger.info( - "Retrieving sync users for user_id: %s, sync_user_id: %s", - user_id, - sync_user_id, - ) - query = ( - self.db.from_("syncs_user").select("*").eq("user_id", user_id) - # .neq("status", "REMOVED") - ) - if sync_user_id: - query = query.eq("id", str(sync_user_id)) - response = query.execute() - if response.data: - # logger.info("Sync users retrieved successfully: %s", response.data) - return response.data - logger.warning( - "No sync users found for user_id: %s, sync_user_id: %s", - user_id, - sync_user_id, - ) - return [] - - def get_sync_user_by_state(self, state: dict) -> SyncsUser | None: - """ - Retrieve a sync user by their state. - - Args: - state (dict): The state to filter sync users. - - Returns: - dict or None: The sync user data matching the state or None if not found. - """ - logger.info("Getting sync user by state: %s", state) - - state_str = json.dumps(state) - response = ( - self.db.from_("syncs_user").select("*").eq("state", state_str).execute() - ) - if response.data and len(response.data) > 0: - logger.info("Sync user found by state: %s", response.data[0]) - sync_user = SyncsUser.model_validate(response.data[0]) - return sync_user - logger.error("No sync user found for state: %s", state) - return None - - def delete_sync_user(self, sync_id: int, user_id: UUID | str): - """ - Delete a sync user from the database. - - Args: - provider (str): The provider of the sync user. - user_id (str): The user ID of the sync user. - """ - logger.info( - "Deleting sync user with sync_id: %s, user_id: %s", sync_id, user_id - ) - self.db.from_("syncs_user").delete().eq("id", sync_id).eq( - "user_id", user_id - ).execute() - - logger.info("Sync user deleted successfully") - - def update_sync_user( - self, sync_user_id: UUID, state: dict, sync_user_input: SyncUserUpdateInput - ): - """ - Update a sync user in the database. - - Args: - sync_user_id (str): The user ID of the sync user. - state (dict): The state to filter sync users. - sync_user_input (SyncUserUpdateInput): The input data for updating the sync user. - """ - logger.info( - "Updating sync user with user_id: %s, state: %s, input: %s", - sync_user_id, - state, - sync_user_input, - ) - - state_str = json.dumps(state) - self.db.from_("syncs_user").update( - sync_user_input.model_dump(exclude_unset=True) - ).eq("user_id", str(sync_user_id)).eq("state", state_str).execute() - logger.info("Sync user updated successfully") - - def update_sync_user_status(self, sync_user_id: int, status: str): - """ - Update the status of a sync user in the database. - - Args: - sync_user_id (str): The user ID of the sync user. - status (str): The new status of the sync user. - """ - logger.info( - "Updating sync user status with user_id: %s, status: %s", - sync_user_id, - status, - ) - - self.db.from_("syncs_user").update({"status": status}).eq( - "id", str(sync_user_id) - ).execute() - logger.info("Sync user status updated successfully") - - def get_all_notion_user_syncs(self): - """ - Retrieve all Notion sync users from the database. - - Returns: - list: A list of Notion sync users. - """ - logger.info("Retrieving all Notion sync users") - response = ( - self.db.from_("syncs_user").select("*").eq("provider", "Notion").execute() - ) - if response.data: - logger.info("Notion sync users retrieved successfully") - return response.data - logger.warning("No Notion sync users found") - return [] - - async def get_files_folder_user_sync( - self, - sync_active_id: int, - user_id: UUID, - notion_service: SyncNotionService | None, - folder_id: str | None = None, - recursive: bool = False, - ) -> None | dict[str, List[SyncFile]] | Literal["No sync found"]: - """ - Retrieve files from a user's sync folder, either from Google Drive or Azure. - - Args: - sync_active_id (int): The ID of the active sync. - user_id (str): The user ID associated with the active sync. - folder_id (str, optional): The folder ID to filter files. Defaults to None. - - Returns: - dict or str: A dictionary containing the list of files or a string indicating the sync provider. - """ - logger.info( - "Retrieving files for user sync with sync_active_id: %s, user_id: %s, folder_id: %s", - sync_active_id, - user_id, - folder_id, - ) - # Check whether the sync is Google or Azure - sync_user = self.get_syncs_user(user_id=user_id, sync_user_id=sync_active_id) - if not sync_user: - logger.warning( - "No sync user found for sync_active_id: %s, user_id: %s", - sync_active_id, - user_id, - ) - return None - - sync_user = sync_user[0] - sync: BaseSync - - provider = sync_user["provider"].lower() - if provider == "google": - logger.info("Getting files for Google sync") - sync = GoogleDriveSync() - return {"files": sync.get_files(sync_user["credentials"], folder_id)} - elif provider == "azure": - logger.info("Getting files for Azure sync") - sync = AzureDriveSync() - return { - "files": sync.get_files(sync_user["credentials"], folder_id, recursive) - } - elif provider == "dropbox": - logger.info("Getting files for Drop Box sync") - sync = DropboxSync() - return { - "files": sync.get_files( - sync_user["credentials"], folder_id if folder_id else "", recursive - ) - } - elif provider == "notion": - if notion_service is None: - raise ValueError("provider notion but notion_service is None") - logger.info("Getting files for Notion sync") - sync = NotionSync(notion_service=notion_service) - return { - "files": await sync.aget_files( - sync_user["credentials"], - folder_id if folder_id else "", - recursive, - sync_user["id"], - ) - } - elif provider == "github": - logger.info("Getting files for GitHub sync") - sync = GitHubSync() - return { - "files": sync.get_files( - sync_user["credentials"], folder_id if folder_id else "", recursive - ) - } - - else: - logger.warning( - "No sync found for provider: %s", sync_user["provider"], recursive - ) - return "No sync found" - - def get_corresponding_deleted_sync(self, user_id: str) -> SyncsUser | None: - """ - Retrieve the deleted sync user from the database. - """ - logger.info( - "Retrieving notion deleted sync user for user_id: %s", - user_id, - ) - response = ( - self.db.from_("syncs_user") - .select("*") - .eq("user_id", user_id) - .eq("provider", "Notion") - .eq("status", "REMOVED") - .execute() - ) - if response.data: - logger.info( - "Deleted sync user retrieved successfully: %s", response.data[0] - ) - return SyncsUser.model_validate(response.data[0]) - logger.error("No deleted notion sync user found for user_id: %s", user_id) - return None diff --git a/backend/api/quivr_api/modules/sync/service/__init__.py b/backend/api/quivr_api/modules/sync/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/service/sync_notion.py b/backend/api/quivr_api/modules/sync/service/sync_notion.py deleted file mode 100644 index 5eca27d57..000000000 --- a/backend/api/quivr_api/modules/sync/service/sync_notion.py +++ /dev/null @@ -1,214 +0,0 @@ -import time -from datetime import datetime, timezone -from typing import List, Sequence -from uuid import UUID - -from notion_client import Client - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseService -from quivr_api.modules.sync.entity.notion_page import NotionPage, NotionSearchResult -from quivr_api.modules.sync.entity.sync_models import NotionSyncFile -from quivr_api.modules.sync.repository.sync_repository import NotionRepository - -logger = get_logger(__name__) - - -class SyncNotionService(BaseService[NotionRepository]): - repository_cls = NotionRepository - - def __init__(self, repository: NotionRepository): - self.repository = repository - - async def create_notion_files( - self, notion_raw_files: List[NotionPage], user_id: UUID, sync_user_id: int - ) -> list[NotionSyncFile]: - pages_to_add: List[NotionSyncFile] = [] - for page in notion_raw_files: - if ( - (page.in_trash is None or not page.in_trash) - and not page.archived - and page.parent.type in ("page_id", "workspace") - ): - pages_to_add.append(page.to_syncfile(user_id, sync_user_id)) - inserted_notion_files = await self.repository.create_notion_files(pages_to_add) - logger.info(f"Insert response {inserted_notion_files}") - return pages_to_add - - async def update_notion_files( - self, - notion_pages: List[NotionPage], - user_id: UUID, - sync_user_id: int, - client: Client, - ) -> bool: - # 1. For each page we check if it is already in the db, if it is we modify it, if it isn't we create it. - # 2. If the page was modified, we check all direct children of the page and check if they stil exist in notion, if they don't, we delete it - # 3. We check if the root folder was deleted, if so we delete the root page & all children - try: - pages_to_delete: list[UUID] = [] - for page in notion_pages: - if ( - not page.in_trash - and not page.archived - and page.parent.type != "database_id" - ): - logger.debug( - "Updating notion file %s ", - page.id, - ) - is_update = await self.repository.update_notion_file( - page.to_syncfile(user_id, sync_user_id) - ) - - if is_update: - logger.info( - f"Updated notion file {page.id}, we need to check if children were deleted" - ) - children = await self.get_notion_files_by_parent_id( - str(page.id), - sync_user_id, - ) - for child in children: - try: - child_notion_page = client.pages.retrieve( - str(child.notion_id) - ) - if ( - child_notion_page["archived"] - or child_notion_page["in_trash"] - ): - pages_to_delete.append(child.notion_id) - except Exception: - logger.error( - f"Page {child.notion_id} is in trash or archived, we are deleting it." - ) - pages_to_delete.append(child.notion_id) - - else: - logger.info(f"Page {page.id} is in trash or archived, skipping ") - - root_pages = await self.get_root_notion_files(sync_user_id=sync_user_id) - - for root_page in root_pages: - root_notion_page = client.pages.retrieve(root_page.notion_id) - if root_notion_page["archived"] or root_notion_page["in_trash"]: - pages_to_delete.append(root_page.notion_id) - logger.info(f"Pages to delete: {pages_to_delete}") - await self.repository.delete_notion_pages(pages_to_delete) - - return True - except Exception as e: - logger.error(f"Error updating notion pages: {e}") - return False - - async def get_notion_files_by_ids(self, ids: List[str]) -> Sequence[NotionSyncFile]: - logger.info(f"Fetching notion files for IDs: {ids}") - notion_files = await self.repository.get_notion_files_by_ids(ids) - logger.info(f"Fetched {len(notion_files)} notion files") - return notion_files - - async def get_notion_files_by_parent_id( - self, parent_id: str | None, sync_user_id: int - ) -> Sequence[NotionSyncFile]: - logger.info(f"Fetching notion files with parent_id: {parent_id}") - notion_files = await self.repository.get_notion_files_by_parent_id( - parent_id, sync_user_id - ) - logger.info( - f"Fetched {len(notion_files)} notion files with parent_id {parent_id}" - ) - return notion_files - - async def get_root_notion_files( - self, sync_user_id: int - ) -> Sequence[NotionSyncFile]: - logger.info("Fetching root notion files") - notion_files = await self.repository.get_notion_files_by_parent_id( - None, sync_user_id - ) - logger.info(f"Fetched {len(notion_files)} root notion files") - return notion_files - - async def is_folder_page(self, page_id: str) -> bool: - logger.info(f"Checking if page is a folder: {page_id}") - is_folder = await self.repository.is_folder_page(page_id) - return is_folder - - async def delete_notion_pages(self, page_id: UUID): - await self.repository.delete_notion_page(page_id) - - -async def update_notion_pages( - notion_service: SyncNotionService, - pages_to_update: list[NotionPage], - user_id: UUID, - sync_user_id: int, - client: Client, -): - return await notion_service.update_notion_files( - pages_to_update, user_id, sync_user_id, client - ) - - -async def store_notion_pages( - all_search_result: list[NotionPage], - notion_service: SyncNotionService, - user_id: UUID, - sync_user_id: int, -): - return await notion_service.create_notion_files( - all_search_result, user_id, sync_user_id - ) - - -def fetch_notion_pages( - notion_client: Client, start_cursor: str | None = None, iteration: int = 0 -) -> NotionSearchResult: - if iteration > 10: - return NotionSearchResult(results=[], has_more=False, next_cursor=None) - search_result = notion_client.search( - query="", - filter={"property": "object", "value": "page"}, - sort={"direction": "descending", "timestamp": "last_edited_time"}, - start_cursor=start_cursor, - ) - if "code" in search_result and search_result["code"] == "rate_limited": - # Wait 10 seconds - time.sleep(10) - search_result = fetch_notion_pages( - notion_client, start_cursor=start_cursor, iteration=iteration + 1 - ) - - return NotionSearchResult.model_validate(search_result) - - -def fetch_limit_notion_pages( - notion_client: Client, - last_sync_time: datetime, -) -> List[NotionPage]: - all_search_result = [] - last_sync_time = last_sync_time.astimezone(timezone.utc) - - search_result = fetch_notion_pages(notion_client) - for page in search_result.results: - if page.last_edited_time > last_sync_time: - all_search_result.append(page) - - if last_sync_time > page.last_edited_time: - return all_search_result - - while search_result.has_more: - logger.debug("next page cursor: %s", search_result.next_cursor) # type: ignore - search_result = fetch_notion_pages( - notion_client, start_cursor=search_result.next_cursor - ) - - for page in search_result.results: - if page.last_edited_time > last_sync_time: - all_search_result.append(page) - - if last_sync_time > page.last_edited_time: - return all_search_result - - return all_search_result diff --git a/backend/api/quivr_api/modules/sync/service/sync_service.py b/backend/api/quivr_api/modules/sync/service/sync_service.py deleted file mode 100644 index 498ba3bc9..000000000 --- a/backend/api/quivr_api/modules/sync/service/sync_service.py +++ /dev/null @@ -1,190 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Dict, List, Union -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.sync.dto.inputs import ( - SyncsActiveInput, - SyncsActiveUpdateInput, - SyncsUserInput, - SyncsUserStatus, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.entity.sync_models import SyncsActive, SyncsUser -from quivr_api.modules.sync.repository.sync_repository import Sync -from quivr_api.modules.sync.repository.sync_user import SyncUserRepository -from quivr_api.modules.sync.service.sync_notion import SyncNotionService - -logger = get_logger(__name__) - - -class ISyncUserService(ABC): - @abstractmethod - def get_syncs_user(self, user_id: UUID, sync_user_id: Union[int, None] = None): - pass - - @abstractmethod - def create_sync_user(self, sync_user_input: SyncsUserInput): - pass - - @abstractmethod - def delete_sync_user(self, sync_id: int, user_id: str): - pass - - @abstractmethod - def get_sync_user_by_state(self, state: Dict) -> Union["SyncsUser", None]: - pass - - @abstractmethod - def get_sync_user_by_id(self, sync_id: int): - pass - - @abstractmethod - def update_sync_user( - self, sync_user_id: UUID, state: Dict, sync_user_input: SyncUserUpdateInput - ): - pass - - @abstractmethod - def get_all_notion_user_syncs(self): - pass - - @abstractmethod - async def get_files_folder_user_sync( - self, - sync_active_id: int, - user_id: UUID, - folder_id: Union[str, None] = None, - recursive: bool = False, - notion_service: Union["SyncNotionService", None] = None, - ): - pass - - -class SyncUserService(ISyncUserService): - def __init__(self): - self.repository = SyncUserRepository() - - def get_syncs_user(self, user_id: UUID, sync_user_id: int | None = None): - return self.repository.get_syncs_user(user_id, sync_user_id) - - def create_sync_user(self, sync_user_input: SyncsUserInput): - if sync_user_input.provider == "Notion": - response = self.repository.get_corresponding_deleted_sync( - user_id=sync_user_input.user_id - ) - if response: - raise ValueError("User removed this connection less than 24 hours ago") - - return self.repository.create_sync_user(sync_user_input) - - def delete_sync_user(self, sync_id: int, user_id: str): - sync_user = self.repository.get_sync_user_by_id(sync_id) - if sync_user and sync_user.provider == "Notion": - sync_user_input = SyncUserUpdateInput( - email=str(sync_user.email), - credentials=sync_user.credentials, - state=sync_user.state, - status=str(SyncsUserStatus.REMOVED), - ) - self.repository.update_sync_user( - sync_user_id=sync_user.user_id, - state=sync_user.state, - sync_user_input=sync_user_input, - ) - return None - else: - return self.repository.delete_sync_user(sync_id, user_id) - - def clean_notion_user_syncs(self): - return self.repository.clean_notion_user_syncs() - - def get_sync_user_by_state(self, state: dict) -> SyncsUser | None: - return self.repository.get_sync_user_by_state(state) - - def get_sync_user_by_id(self, sync_id: int): - return self.repository.get_sync_user_by_id(sync_id) - - def update_sync_user( - self, sync_user_id: UUID, state: dict, sync_user_input: SyncUserUpdateInput - ): - return self.repository.update_sync_user(sync_user_id, state, sync_user_input) - - def update_sync_user_status(self, sync_user_id: int, status: str): - return self.repository.update_sync_user_status(sync_user_id, status) - - def get_all_notion_user_syncs(self): - return self.repository.get_all_notion_user_syncs() - - async def get_files_folder_user_sync( - self, - sync_active_id: int, - user_id: UUID, - folder_id: str | None = None, - recursive: bool = False, - notion_service: SyncNotionService | None = None, - ): - return await self.repository.get_files_folder_user_sync( - sync_active_id=sync_active_id, - user_id=user_id, - folder_id=folder_id, - recursive=recursive, - notion_service=notion_service, - ) - - -class ISyncService(ABC): - @abstractmethod - def create_sync_active( - self, sync_active_input: SyncsActiveInput, user_id: str - ) -> Union["SyncsActive", None]: - pass - - @abstractmethod - def get_syncs_active(self, user_id: str) -> List[SyncsActive]: - pass - - @abstractmethod - def update_sync_active( - self, sync_id: int, sync_active_input: SyncsActiveUpdateInput - ): - pass - - @abstractmethod - def delete_sync_active(self, sync_active_id: int, user_id: UUID): - pass - - @abstractmethod - async def get_syncs_active_in_interval(self) -> List[SyncsActive]: - pass - - @abstractmethod - def get_details_sync_active(self, sync_active_id: int): - pass - - -class SyncService(ISyncService): - def __init__(self): - self.repository = Sync() - - def create_sync_active( - self, sync_active_input: SyncsActiveInput, user_id: str - ) -> SyncsActive | None: - return self.repository.create_sync_active(sync_active_input, user_id) - - def get_syncs_active(self, user_id: str) -> List[SyncsActive]: - return self.repository.get_syncs_active(user_id) - - def update_sync_active( - self, sync_id: int, sync_active_input: SyncsActiveUpdateInput - ): - return self.repository.update_sync_active(sync_id, sync_active_input) - - def delete_sync_active(self, sync_active_id: int, user_id: UUID): - return self.repository.delete_sync_active(sync_active_id, user_id) - - async def get_syncs_active_in_interval(self) -> List[SyncsActive]: - return await self.repository.get_syncs_active_in_interval() - - def get_details_sync_active(self, sync_active_id: int): - return self.repository.get_details_sync_active(sync_active_id) diff --git a/backend/api/quivr_api/modules/sync/tests/__init__.py b/backend/api/quivr_api/modules/sync/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/tests/conftest.py b/backend/api/quivr_api/modules/sync/tests/conftest.py deleted file mode 100644 index 32392aeeb..000000000 --- a/backend/api/quivr_api/modules/sync/tests/conftest.py +++ /dev/null @@ -1,817 +0,0 @@ -import json -import os -import uuid -from collections import defaultdict -from datetime import datetime, timedelta -from io import BytesIO -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Union -from uuid import UUID, uuid4 - -import pytest -import pytest_asyncio -from dotenv import load_dotenv -from sqlmodel import select, text - -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.brain.repository.brains_vectors import BrainsVectors -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.dto.inputs import ( - CreateNotification, - NotificationUpdatableProperties, -) -from quivr_api.modules.notification.entity.notification import ( - Notification, - NotificationsStatusEnum, -) -from quivr_api.modules.notification.repository.notifications_interface import ( - NotificationInterface, -) -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.dto.inputs import ( - SyncFileInput, - SyncFileUpdateInput, - SyncsActiveInput, - SyncsActiveUpdateInput, - SyncsUserInput, - SyncUserUpdateInput, -) -from quivr_api.modules.sync.entity.notion_page import ( - BlockParent, - DatabaseParent, - NotionPage, - NotionSearchResult, - PageParent, - PageProps, - TextContent, - Title, - TitleItem, - WorkspaceParent, -) -from quivr_api.modules.sync.entity.sync_models import ( - DBSyncFile, - NotionSyncFile, - SyncFile, - SyncsActive, - SyncsUser, -) -from quivr_api.modules.sync.repository.sync_interfaces import SyncFileInterface -from quivr_api.modules.sync.service.sync_notion import SyncNotionService -from quivr_api.modules.sync.service.sync_service import ( - ISyncService, - ISyncUserService, - SyncUserService, -) -from quivr_api.modules.sync.utils.sync import ( - BaseSync, -) -from quivr_api.modules.sync.utils.syncutils import ( - SyncUtils, -) -from quivr_api.modules.user.entity.user_identity import User - -pg_database_base_url = "postgres:postgres@localhost:54322/postgres" -load_dotenv() - - -@pytest.fixture(scope="function") -def page_response() -> dict[str, Any]: - json_path = ( - Path(os.getenv("PYTEST_CURRENT_TEST").split(":")[0]) - .parent.absolute() - .joinpath("page.json") - ) - with open(json_path, "r") as f: - page = json.load(f) - return page - - -@pytest.fixture(scope="function") -def fetch_response(): - return [ - { - "object": "page", - "id": "27b26c5a-e86f-470a-a5fc-27a3fc308850", - "created_time": "2024-05-02T09:03:00.000Z", - "last_edited_time": "2024-08-19T10:01:00.000Z", - "created_by": { - "object": "user", - "id": "e2f8bfda-3b98-466e-a2c1-39e5f0f64881", - }, - "last_edited_by": { - "object": "user", - "id": "f87bcc4b-68ee-4d44-b518-3d2d19ffedc2", - }, - "cover": None, - "icon": {"type": "emoji", "emoji": "🌇"}, - "parent": {"type": "workspace", "workspace": True}, - "archived": False, - "in_trash": False, - "properties": { - "title": { - "id": "title", - "type": "title", - "title": [ - { - "type": "text", - "text": {"content": "Investors", "link": None}, - "annotations": { - "bold": False, - "italic": False, - "strikethrough": False, - "underline": False, - "code": False, - "color": "default", - }, - "plain_text": "Investors", - "href": None, - } - ], - } - }, - "url": "https://www.notion.so/Investors-27b26c5ae86f470aa5fc27a3fc308850", - "public_url": None, - }, - { - "object": "page", - "id": "ff799030-eae6-4c81-8631-ee2653f27af8", - "created_time": "2024-04-04T23:24:00.000Z", - "last_edited_time": "2024-08-19T10:01:00.000Z", - "created_by": { - "object": "user", - "id": "c8de6079-cc5a-4b46-8763-04f92b33fc18", - }, - "last_edited_by": { - "object": "user", - "id": "f87bcc4b-68ee-4d44-b518-3d2d19ffedc2", - }, - "cover": None, - "icon": {"type": "emoji", "emoji": "🎓"}, - "parent": {"type": "workspace", "workspace": True}, - "archived": False, - "in_trash": False, - "properties": { - "title": { - "id": "title", - "type": "title", - "title": [ - { - "type": "text", - "text": {"content": "Academy", "link": None}, - "annotations": { - "bold": False, - "italic": False, - "strikethrough": False, - "underline": False, - "code": False, - "color": "default", - }, - "plain_text": "Academy", - "href": None, - } - ], - } - }, - "url": "https://www.notion.so/Academy-ff799030eae64c818631ee2653f27af8", - "public_url": None, - }, - ] - - -@pytest.fixture -def search_result(): - return [ - { - "object": "page", - "id": "77b34b29-96f5-487c-b594-ba69cbb951c0", - "created_time": "2024-07-29T16:58:00.000Z", - "last_edited_time": "2024-07-30T07:46:00.000Z", - "created_by": { - "object": "user", - "id": "c8de6079-cc5a-4b46-8763-04f92b33fc18", - }, - "last_edited_by": { - "object": "user", - "id": "c8de6079-cc5a-4b46-8763-04f92b33fc18", - }, - "cover": None, - "icon": {"type": "emoji", "emoji": ":brain:"}, - "parent": { - "type": "page_id", - "page_id": "6df769b6-3849-4141-b61c-14f0f6d4fa43", - }, - "archived": False, - "in_trash": False, - "properties": { - "title": { - "id": "title", - "type": "title", - "title": [ - { - "type": "text", - "text": {"content": "MEDDPICC", "link": None}, - "annotations": { - "bold": False, - "italic": False, - "strikethrough": False, - "underline": False, - "code": False, - "color": "default", - }, - "plain_text": "MEDDPICC", - "href": None, - } - ], - } - }, - "url": "https://www.notion.so/MEDDPICC-77b34b2996f5487cb594ba69cbb951c0", - "public_url": None, - } - ] - - -@pytest.fixture(scope="function") -def user_1(sync_session) -> User: - user_1 = ( - sync_session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - return user_1 - - -@pytest.fixture( - params=[ - PageParent(type="page_id", page_id=uuid4()), - WorkspaceParent(type="workspace", workspace=True), - ] -) -def notion_search_result(request) -> NotionSearchResult: - return NotionSearchResult( - results=[ - NotionPage( - id=uuid4(), - created_time=datetime.now(), - last_edited_time=datetime.now(), - url="url", - archived=False, - in_trash=False, - public_url=None, - parent=request.param, - cover=None, - icon=None, - properties=PageProps( - title=Title( - id="id_title", - type="title", - title=[ - TitleItem( - type="text", - text=TextContent( - content="title", - link=None, - ), - annotations={}, - plain_text="", - ) - ], - ) - ), - ) - ], - has_more=False, - next_cursor=None, - ) - - -@pytest.fixture( - params=[ - DatabaseParent(type="database_id", database_id=uuid4()), - BlockParent(type="block_id", block_id=uuid4()), - ] -) -def notion_search_result_bad_parent(request) -> NotionSearchResult: - return NotionSearchResult( - results=[ - NotionPage( - id=uuid4(), - created_time=datetime.now(), - last_edited_time=datetime.now(), - url="url", - archived=False, - in_trash=False, - public_url=None, - parent=request.param, - cover=None, - icon=None, - properties=PageProps( - title=Title( - id="id_title", - type="title", - title=[ - TitleItem( - type="text", - text=TextContent( - content="title", - link=None, - ), - annotations={}, - plain_text="", - ) - ], - ) - ), - ) - ], - has_more=False, - next_cursor=None, - ) - - -class MockSyncCloud(BaseSync): - # TODO: Mock notion - name = "mockcloud" - lower_name = "mockcloud" - datetime_format: str = "%Y-%m-%d %H:%M:%S" - - def get_files_by_id(self, credentials: Dict, file_ids: List[str]) -> List[SyncFile]: - raise NotImplementedError - - def get_files( - self, credentials: Dict, folder_id: str | None = None, recursive: bool = False - ) -> List[SyncFile]: - raise NotImplementedError() - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - raise NotImplementedError - - # Implement async only - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - return [ - SyncFile( - id=fid, - name=f"file_{fid}", - is_folder=False, - last_modified=datetime.now().strftime(self.datetime_format), - mime_type="txt", - web_view_link=f"{self.name}/{fid}", - ) - for fid in file_ids - ] - - async def aget_files( - self, - credentials: Dict, - sync_user_id=int, - folder_id: str | None = None, - recursive: bool = False, - ) -> List[SyncFile]: - n_files = 1 - return [ - SyncFile( - id=str(uuid4()), - name=f"file_in_{folder_id}", - is_folder=False, - last_modified=datetime.now().strftime(self.datetime_format), - mime_type="txt", - web_view_link=f"{self.name}/{fid}", - ) - for fid in range(n_files) - ] - - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - return {"file_name": file.name, "content": BytesIO(os.urandom(128))} - - def check_and_refresh_access_token(self, credentials: dict) -> Dict: - return credentials - - -class MockSyncCloudNotion(MockSyncCloud): - # TODO: Mock notion - name = "notion" - lower_name = "notion" - datetime_format: str = "%Y-%m-%d %H:%M:%S" - - -class MockNotification(NotificationInterface): - def __init__( - self, - notification_ids: list[UUID], - user_id: UUID, - brain_id: UUID, - ): - self.received: dict[UUID, Notification] = {} - for notification_id in notification_ids: - self.received[notification_id] = Notification( - id=notification_id, - user_id=user_id, - status=NotificationsStatusEnum.INFO, - category="sync", - title="test", - description="", - datetime=datetime.now(), - brain_id=str(brain_id), - ) - - def add_notification(self, notification: CreateNotification) -> Notification: - notif = Notification( - datetime=datetime.now(), id=uuid4(), **notification.model_dump() - ) - self.received[notif.id] = notif - return notif - - def update_notification_by_id( - self, notification_id: UUID, notification: NotificationUpdatableProperties - ) -> Notification: - prev_notif = self.received[notification_id] - prev_notif = Notification( - **{**prev_notif.model_dump(), **notification.model_dump()} - ) - self.received[notification_id] = prev_notif - return prev_notif - - def remove_notification_by_id(self, notification_id: UUID): - del self.received[notification_id] - - -class MockSyncService(ISyncService): - def __init__(self, sync_active: SyncsActive): - self.syncs_active_user = {} - self.syncs_active_id = {} - self.syncs_active_user[sync_active.user_id] = sync_active - self.syncs_active_id[sync_active.id] = sync_active - - def create_sync_active( - self, - sync_active_input: SyncsActiveInput, - user_id: str, - ) -> SyncsActive | None: - sactive = SyncsActive( - id=len(self.syncs_active_user) + 1, - user_id=UUID(user_id), - **sync_active_input.model_dump(), - ) - self.syncs_active_user[user_id] = sactive - return sactive - - def get_syncs_active(self, user_id: str) -> List[SyncsActive]: - return self.syncs_active_user[user_id] - - def update_sync_active( - self, sync_id: int, sync_active_input: SyncsActiveUpdateInput - ): - sync = self.syncs_active_id[sync_id] - sync = SyncsActive(**sync.model_dump(), **sync_active_input.model_dump()) - self.syncs_active_id[sync_id] = sync - return sync - - def delete_sync_active(self, sync_active_id: int, user_id: UUID): - del self.syncs_active_id[sync_active_id] - del self.syncs_active_user[user_id] - - async def get_syncs_active_in_interval(self) -> List[SyncsActive]: - return list(self.syncs_active_id.values()) - - def get_details_sync_active(self, sync_active_id: int): - return - - -class MockSyncUserService(ISyncUserService): - def __init__(self, sync_user: SyncsUser): - self.map_id = {} - self.map_userid = {} - self.map_id[sync_user.id] = sync_user - self.map_userid[sync_user.id] = sync_user - - def get_syncs_user(self, user_id: UUID, sync_user_id: int | None = None): - return self.map_userid[user_id] - - def get_sync_user_by_id(self, sync_id: int): - return self.map_id[sync_id] - - def create_sync_user(self, sync_user_input: SyncsUserInput): - id = len(self.map_userid) + 1 - self.map_userid[sync_user_input.user_id] = SyncsUser( - id=id, **sync_user_input.model_dump() - ) - self.map_id[id] = self.map_userid[sync_user_input.user_id] - return self.map_id[id] - - def delete_sync_user(self, sync_id: int, user_id: str): - del self.map_userid[user_id] - del self.map_userid[sync_id] - - def get_sync_user_by_state(self, state: dict) -> SyncsUser | None: - return list(self.map_userid.values())[-1] - - def update_sync_user( - self, sync_user_id: UUID, state: dict, sync_user_input: SyncUserUpdateInput - ): - return - - def get_all_notion_user_syncs(self): - return - - async def get_files_folder_user_sync( - self, - sync_active_id: int, - user_id: UUID, - folder_id: str | None = None, - recursive: bool = False, - notion_service: SyncNotionService | None = None, - ): - return - - -class MockSyncFilesRepository(SyncFileInterface): - def __init__(self): - self.files_store = defaultdict(list) - self.next_id = 1 - - def create_sync_file(self, sync_file_input: SyncFileInput) -> Optional[DBSyncFile]: - supported = sync_file_input.supported if sync_file_input.supported else True - new_file = DBSyncFile( - id=self.next_id, - path=sync_file_input.path, - syncs_active_id=sync_file_input.syncs_active_id, - last_modified=sync_file_input.last_modified, - brain_id=sync_file_input.brain_id, - supported=supported, - ) - self.files_store[sync_file_input.syncs_active_id].append(new_file) - self.next_id += 1 - return new_file - - def get_sync_files(self, sync_active_id: int) -> List[DBSyncFile]: - """ - Retrieve sync files from the mock database. - - Args: - sync_active_id (int): The ID of the active sync. - - Returns: - List[DBSyncFile]: A list of sync files matching the criteria. - """ - return self.files_store[sync_active_id] - - def update_sync_file( - self, sync_file_id: int, sync_file_input: SyncFileUpdateInput - ) -> None: - for sync_files in self.files_store.values(): - for file in sync_files: - if file.id == sync_file_id: - update_data = sync_file_input.model_dump(exclude_unset=True) - if "last_modified" in update_data: - file.last_modified = update_data["last_modified"] - if "supported" in update_data: - file.supported = update_data["supported"] - return - - def update_or_create_sync_file( - self, - file: SyncFile, - sync_active: SyncsActive, - previous_file: Optional[DBSyncFile], - supported: bool, - ) -> Optional[DBSyncFile]: - if previous_file: - self.update_sync_file( - previous_file.id, - SyncFileUpdateInput( - last_modified=file.last_modified, - supported=previous_file.supported or supported, - ), - ) - return previous_file - else: - return self.create_sync_file( - SyncFileInput( - path=file.name, - syncs_active_id=sync_active.id, - last_modified=file.last_modified, - brain_id=str(sync_active.brain_id), - supported=supported, - ) - ) - - def delete_sync_file(self, sync_file_id: int) -> None: - for sync_active_id, sync_files in self.files_store.items(): - self.files_store[sync_active_id] = [ - file for file in sync_files if file.id != sync_file_id - ] - - -@pytest.fixture -def sync_file(): - file = SyncFile( - id=str(uuid4()), - name="test_file.txt", - is_folder=False, - last_modified=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - mime_type=".txt", - web_view_link="", - notification_id=uuid4(), # - ) - return file - - -@pytest.fixture -def prev_file(): - file = SyncFile( - id=str(uuid4()), - name="test_file.txt", - is_folder=False, - last_modified=(datetime.now() - timedelta(hours=1)).strftime( - "%Y-%m-%d %H:%M:%S" - ), - mime_type="txt", - web_view_link="", - notification_id=uuid4(), # - ) - return file - - -@pytest_asyncio.fixture(scope="function") -async def brain_user_setup( - session, -) -> Tuple[Brain, User]: - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - # Brain data - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - - session.add(brain_1) - await session.refresh(user_1) - await session.commit() - assert user_1 - assert brain_1.brain_id - return brain_1, user_1 - - -@pytest_asyncio.fixture(scope="function") -async def sync_user_notion_setup( - session, -): - sync_user_service = SyncUserService() - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - - # Sync User - sync_user_input = SyncsUserInput( - user_id=str(user_1.id), - name="sync_user_1", - provider="notion", - credentials={}, - state={}, - additional_data={}, - status="", - ) - sync_user = SyncsUser.model_validate( - sync_user_service.create_sync_user(sync_user_input) - ) - assert sync_user.id - - # Notion pages - notion_page_1 = NotionSyncFile( - notion_id=uuid.uuid4(), - sync_user_id=sync_user.id, - user_id=sync_user.user_id, - name="test", - last_modified=datetime.now() - timedelta(hours=5), - mime_type="txt", - web_view_link="", - icon="", - is_folder=False, - ) - - notion_page_2 = NotionSyncFile( - notion_id=uuid.uuid4(), - sync_user_id=sync_user.id, - user_id=sync_user.user_id, - name="test_2", - last_modified=datetime.now() - timedelta(hours=5), - mime_type="txt", - web_view_link="", - icon="", - is_folder=False, - ) - session.add(notion_page_1) - session.add(notion_page_2) - yield sync_user - await session.execute( - text("DELETE FROM syncs_user WHERE id = :sync_id"), {"sync_id": sync_user.id} - ) - - -@pytest_asyncio.fixture(scope="function") -async def setup_syncs_data( - brain_user_setup, -) -> Tuple[SyncsUser, SyncsActive]: - brain_1, user_1 = brain_user_setup - - sync_user = SyncsUser( - id=0, - user_id=user_1.id, - name="c8xfz3g566b8xa1ajiesdh", - provider="mock", - credentials={}, - state={}, - additional_data={}, - status="", - ) - sync_active = SyncsActive( - id=0, - name="test", - syncs_user_id=sync_user.id, - user_id=sync_user.user_id, - settings={}, - last_synced=str(datetime.now() - timedelta(hours=5)), - sync_interval_minutes=1, - brain_id=brain_1.brain_id, - ) - - return (sync_user, sync_active) - - -@pytest.fixture -def syncutils( - sync_file: SyncFile, - prev_file: SyncFile, - setup_syncs_data: Tuple[SyncsUser, SyncsActive], - session, -) -> SyncUtils: - (sync_user, sync_active) = setup_syncs_data - assert sync_file.notification_id - sync_active_service = MockSyncService(sync_active) - sync_user_service = MockSyncUserService(sync_user) - sync_files_repo_service = MockSyncFilesRepository() - knowledge_service = KnowledgeService(KnowledgeRepository(session)) - notification_service = NotificationService( - repository=MockNotification( - [sync_file.notification_id, prev_file.notification_id], # type: ignore - sync_user.user_id, - sync_active.brain_id, - ) - ) - brain_vectors = BrainsVectors() - sync_cloud = MockSyncCloud() - - sync_util = SyncUtils( - sync_user_service=sync_user_service, - sync_active_service=sync_active_service, - sync_files_repo=sync_files_repo_service, - sync_cloud=sync_cloud, - notification_service=notification_service, - brain_vectors=brain_vectors, - knowledge_service=knowledge_service, - ) - - return sync_util - - -@pytest.fixture -def syncutils_notion( - sync_file: SyncFile, - prev_file: SyncFile, - setup_syncs_data: Tuple[SyncsUser, SyncsActive], - session, -) -> SyncUtils: - (sync_user, sync_active) = setup_syncs_data - assert sync_file.notification_id - sync_active_service = MockSyncService(sync_active) - sync_user_service = MockSyncUserService(sync_user) - sync_files_repo_service = MockSyncFilesRepository() - knowledge_service = KnowledgeService(KnowledgeRepository(session)) - notification_service = NotificationService( - repository=MockNotification( - [sync_file.notification_id, prev_file.notification_id], # type: ignore - sync_user.user_id, - sync_active.brain_id, - ) - ) - brain_vectors = BrainsVectors() - sync_cloud = MockSyncCloudNotion() - sync_util = SyncUtils( - sync_user_service=sync_user_service, - sync_active_service=sync_active_service, - sync_files_repo=sync_files_repo_service, - sync_cloud=sync_cloud, - notification_service=notification_service, - brain_vectors=brain_vectors, - knowledge_service=knowledge_service, - ) - - return sync_util diff --git a/backend/api/quivr_api/modules/sync/tests/page.json b/backend/api/quivr_api/modules/sync/tests/page.json deleted file mode 100644 index 84eaa2764..000000000 --- a/backend/api/quivr_api/modules/sync/tests/page.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "object": "page", - "id": "be633bf1-dfa0-436d-b259-571129a590e5", - "created_time": "2022-10-24T22:54:00.000Z", - "last_edited_time": "2023-03-08T18:25:00.000Z", - "created_by": { - "object": "user", - "id": "c2f20311-9e54-4d11-8c79-7398424ae41e" - }, - "last_edited_by": { - "object": "user", - "id": "9188c6a5-7381-452f-b3dc-d4865aa89bdf" - }, - "cover": null, - "icon": { - "type": "emoji", - "emoji": "ðŸž" - }, - - "parent": { - "type": "database_id", - "database_id": "a1d8501e-1ac1-43e9-a6bd-ea9fe6c8822b" - }, - "archived": true, - "in_trash": true, - "properties": { - "Due date": { - "id": "M%3BBw", - "type": "date", - "date": { - "start": "2023-02-23", - "end": null, - "time_zone": null - } - }, - "Status": { - "id": "Z%3ClH", - "type": "status", - "status": { - "id": "86ddb6ec-0627-47f8-800d-b65afd28be13", - "name": "Not started", - "color": "default" - } - }, - "Title": { - "id": "title", - "type": "title", - "title": [ - { - "type": "text", - "text": { - "content": "Bug bash", - "link": null - }, - "annotations": { - "bold": false, - "italic": false, - "strikethrough": false, - "underline": false, - "code": false, - "color": "default" - }, - "plain_text": "Bug bash", - "href": null - } - ] - } - }, - "url": "https://www.notion.so/Bug-bash-be633bf1dfa0436db259571129a590e5", - "public_url": "https://jm-testing.notion.site/p1-6df2c07bfc6b4c46815ad205d132e22d" -} diff --git a/backend/api/quivr_api/modules/sync/tests/test_notion_service.py b/backend/api/quivr_api/modules/sync/tests/test_notion_service.py deleted file mode 100644 index 526114c5e..000000000 --- a/backend/api/quivr_api/modules/sync/tests/test_notion_service.py +++ /dev/null @@ -1,133 +0,0 @@ -from datetime import datetime -from typing import Tuple - -import httpx -import pytest -from notion_client.client import Client -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_api.modules.brain.integrations.Notion.Notion_connector import NotionPage -from quivr_api.modules.sync.entity.notion_page import NotionSearchResult -from quivr_api.modules.sync.entity.sync_models import SyncsActive, SyncsUser -from quivr_api.modules.sync.repository.sync_repository import NotionRepository -from quivr_api.modules.sync.service.sync_notion import ( - SyncNotionService, - fetch_limit_notion_pages, - fetch_notion_pages, - store_notion_pages, -) -from quivr_api.modules.user.entity.user_identity import User - - -def test_deserialize_notion_page(fetch_response): - page = NotionPage.model_validate(fetch_response[0]) # type: ignore - assert page - - -def test_fetch_notion_pages(fetch_response): - def handler(request): - return httpx.Response( - 200, - json={"results": fetch_response, "has_more": False, "next_cursor": None}, - ) - - _client = httpx.Client(transport=httpx.MockTransport(handler)) - notion_client = Client(client=_client) - - result = fetch_notion_pages(notion_client) - assert len(result.results) == 2 - assert not result.has_more - assert result.next_cursor is None - - -# TODO(@aminediro): test more cases: noresponse, error, no has_more .. -def test_fetch_limit_notion_pages(fetch_response): - def handler(request): - return httpx.Response( - 200, - json={"results": fetch_response, "has_more": False, "next_cursor": None}, - ) - - _client = httpx.Client(transport=httpx.MockTransport(handler)) - notion_client = Client(client=_client) - - result = fetch_limit_notion_pages( - notion_client=notion_client, - last_sync_time=datetime(1970, 1, 1, 0, 0, 0), # UNIX EPOCH - ) - - assert len(result) == len(fetch_response) - - -def test_fetch_limit_notion_pages_now(fetch_response): - def handler(request): - return httpx.Response( - 200, - json={"results": fetch_response, "has_more": False, "next_cursor": None}, - ) - - _client = httpx.Client(transport=httpx.MockTransport(handler)) - notion_client = Client(client=_client) - - result = fetch_limit_notion_pages(notion_client, datetime.now()) - - assert len(result) == 0 - - -@pytest.mark.skip( - reason="Bug: httpx.ConnectError: [Errno -2] Name or service not known'" -) -@pytest.mark.asyncio(loop_scope="session") -async def test_store_notion_pages_success( - session: AsyncSession, - notion_search_result: NotionSearchResult, - setup_syncs_data: Tuple[SyncsUser, SyncsActive], - sync_user_notion_setup: SyncsUser, - user_1: User, -): - assert user_1.id - - notion_repository = NotionRepository(session) - notion_service = SyncNotionService(notion_repository) - sync_files = await store_notion_pages( - notion_search_result.results, - notion_service, - user_1.id, - sync_user_id=sync_user_notion_setup.id, - ) - assert sync_files - assert len(sync_files) == 1 - assert sync_files[0].notion_id == notion_search_result.results[0].id - assert sync_files[0].mime_type == "md" - - -@pytest.mark.asyncio(loop_scope="session") -async def test_store_notion_pages_fail( - session: AsyncSession, - notion_search_result_bad_parent: NotionSearchResult, - user_1: User, -): - assert user_1.id - notion_repository = NotionRepository(session) - notion_service = SyncNotionService(notion_repository) - sync_files = await store_notion_pages( - notion_search_result_bad_parent.results, - notion_service, - user_1.id, - sync_user_id=0, # FIXME - ) - assert len(sync_files) == 0 - - -# @pytest.mark.asyncio(loop_scope="session") -# async def test_cascade_delete_notion_sync( -# session: AsyncSession, user_1: User, sync_user_notion_setup: SyncsUser -# ): -# assert user_1.id -# assert sync_user_notion_setup.id -# sync_user_service = SyncUserService() -# sync_user_service.delete_sync_user(sync_user_notion_setup.id, str(user_1.id)) - -# query = sqlselect(NotionSyncFile).where(NotionSyncFile.sync_user_id == SyncsUser.id) -# response = await session.exec(query) -# assert response.all() == [] diff --git a/backend/api/quivr_api/modules/sync/tests/test_syncutils.py b/backend/api/quivr_api/modules/sync/tests/test_syncutils.py deleted file mode 100644 index 3c20f70d9..000000000 --- a/backend/api/quivr_api/modules/sync/tests/test_syncutils.py +++ /dev/null @@ -1,452 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Tuple -from uuid import uuid4 - -import pytest - -from quivr_api.modules.brain.entity.brain_entity import Brain -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.sync.entity.sync_models import ( - DBSyncFile, - SyncFile, - SyncsActive, - SyncsUser, -) -from quivr_api.modules.sync.utils.syncutils import ( - SyncUtils, - filter_on_supported_files, - should_download_file, -) -from quivr_api.modules.upload.service.upload_file import check_file_exists -from quivr_api.modules.user.entity.user_identity import User - - -def test_filter_on_supported_files_empty_existing(): - files = [ - SyncFile( - id="1", - name="file_name", - is_folder=True, - last_modified=str(datetime.now()), - mime_type="txt", - web_view_link="link", - ) - ] - existing_file = {} - - assert [(files[0], None)] == filter_on_supported_files(files, existing_file) - - -def test_filter_on_supported_files_prev_not_supported(): - files = [ - SyncFile( - id=f"{idx}", - name=f"file_name_{idx}", - is_folder=False, - last_modified=str(datetime.now()), - mime_type="txt", - web_view_link="link", - ) - for idx in range(3) - ] - existing_files = { - file.name: DBSyncFile( - id=idx, - path=file.name, - syncs_active_id=1, - last_modified=str(datetime.now()), - brain_id=str(uuid4()), - supported=idx % 2 == 0, - ) - for idx, file in enumerate(files) - } - - assert [ - (files[idx], existing_files[f"file_name_{idx}"]) - for idx in range(3) - if idx % 2 == 0 - ] == filter_on_supported_files(files, existing_files) - - -def test_should_download_file_no_sync_time_not_folder(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=False, - last_modified=datetime.now().strftime(datetime_format), - mime_type="txt", - web_view_link="link", - ) - assert should_download_file( - file=file_not_folder, - last_updated_sync_active=None, - provider_name="google", - datetime_format=datetime_format, - ) - - -def test_should_download_file_no_sync_time_folder(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=True, - last_modified=datetime.now().strftime(datetime_format), - mime_type="txt", - web_view_link="link", - ) - assert not should_download_file( - file=file_not_folder, - last_updated_sync_active=None, - provider_name="google", - datetime_format=datetime_format, - ) - - -def test_should_download_file_notiondb(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=False, - last_modified=datetime.now().strftime(datetime_format), - mime_type="db", - web_view_link="link", - ) - - assert not should_download_file( - file=file_not_folder, - last_updated_sync_active=(datetime.now() - timedelta(hours=5)).astimezone( - timezone.utc - ), - provider_name="notion", - datetime_format=datetime_format, - ) - - -def test_should_download_file_not_notiondb(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=False, - last_modified=datetime.now().strftime(datetime_format), - mime_type="md", - web_view_link="link", - ) - - assert should_download_file( - file=file_not_folder, - last_updated_sync_active=None, - provider_name="notion", - datetime_format=datetime_format, - ) - - -def test_should_download_file_lastsynctime_before(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=False, - last_modified=datetime.now().strftime(datetime_format), - mime_type="txt", - web_view_link="link", - ) - last_sync_time = (datetime.now() - timedelta(hours=5)).astimezone(timezone.utc) - - assert should_download_file( - file=file_not_folder, - last_updated_sync_active=last_sync_time, - provider_name="google", - datetime_format=datetime_format, - ) - - -def test_should_download_file_lastsynctime_after(): - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - file_not_folder = SyncFile( - id="1", - name="file_name", - is_folder=False, - last_modified=(datetime.now() - timedelta(hours=5)).strftime(datetime_format), - mime_type="txt", - web_view_link="link", - ) - last_sync_time = datetime.now().astimezone(timezone.utc) - - assert not should_download_file( - file=file_not_folder, - last_updated_sync_active=last_sync_time, - provider_name="google", - datetime_format=datetime_format, - ) - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_syncfiles_from_ids_nofolder(syncutils: SyncUtils): - files = await syncutils.get_syncfiles_from_ids( - credentials={}, files_ids=[str(uuid4())], folder_ids=[], sync_user_id=1 - ) - assert len(files) == 1 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_syncfiles_from_ids_folder(syncutils: SyncUtils): - files = await syncutils.get_syncfiles_from_ids( - credentials={}, - files_ids=[str(uuid4())], - folder_ids=[str(uuid4())], - sync_user_id=0, - ) - assert len(files) == 2 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_get_syncfiles_from_ids_notion(syncutils_notion: SyncUtils): - files = await syncutils_notion.get_syncfiles_from_ids( - credentials={}, - files_ids=[str(uuid4())], - folder_ids=[str(uuid4())], - sync_user_id=0, - ) - assert len(files) == 3 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_download_file(syncutils: SyncUtils): - file = SyncFile( - id=str(uuid4()), - name="test_file.txt", - is_folder=False, - last_modified=datetime.now().strftime(syncutils.sync_cloud.datetime_format), - mime_type="txt", - web_view_link="", - ) - dfile = await syncutils.download_file(file, {}) - assert dfile.extension == ".txt" - assert dfile.file_name == file.name - assert len(dfile.file_data.read()) > 0 - - -@pytest.mark.asyncio(loop_scope="session") -async def test_process_sync_file_not_supported(syncutils: SyncUtils): - file = SyncFile( - id=str(uuid4()), - name="test_file.asldkjfalsdkjf", - is_folder=False, - last_modified=datetime.now().strftime(syncutils.sync_cloud.datetime_format), - mime_type="txt", - web_view_link="", - notification_id=uuid4(), # - ) - brain_id = uuid4() - sync_user = SyncsUser( - id=1, - user_id=uuid4(), - name="c8xfz3g566b8xa1ajiesdh", - provider="mock", - credentials={}, - state={}, - additional_data={}, - status="", - ) - sync_active = SyncsActive( - id=1, - name="test", - syncs_user_id=1, - user_id=sync_user.user_id, - settings={}, - last_synced=str(datetime.now() - timedelta(hours=5)), - sync_interval_minutes=1, - brain_id=brain_id, - ) - - with pytest.raises(ValueError): - await syncutils.process_sync_file( - file=file, - previous_file=None, - current_user=sync_user, - sync_active=sync_active, - ) - - -@pytest.mark.skip( - reason="Bug: UnboundLocalError: cannot access local variable 'response'" -) -@pytest.mark.asyncio(loop_scope="session") -async def test_process_sync_file_noprev( - monkeypatch, - brain_user_setup: Tuple[Brain, User], - setup_syncs_data: Tuple[SyncsUser, SyncsActive], - syncutils: SyncUtils, - sync_file: SyncFile, -): - task = {} - - def _send_task(*args, **kwargs): - task["args"] = args - task["kwargs"] = {**kwargs["kwargs"]} - - monkeypatch.setattr("quivr_api.celery_config.celery.send_task", _send_task) - - brain_1, _ = brain_user_setup - assert brain_1.brain_id - (sync_user, sync_active) = setup_syncs_data - await syncutils.process_sync_file( - file=sync_file, - previous_file=None, - current_user=sync_user, - sync_active=sync_active, - ) - - # Check notification inserted - assert ( - sync_file.notification_id in syncutils.notification_service.repository.received # type: ignore - ) - assert ( - syncutils.notification_service.repository.received[ # type: ignore - sync_file.notification_id # type: ignore - ].status - == NotificationsStatusEnum.SUCCESS - ) - - # Check Syncfile created - dbfiles: list[DBSyncFile] = syncutils.sync_files_repo.get_sync_files(sync_active.id) - assert len(dbfiles) == 1 - assert dbfiles[0].brain_id == str(brain_1.brain_id) - assert dbfiles[0].syncs_active_id == sync_active.id - assert dbfiles[0].supported - - # Check knowledge created - all_km = await syncutils.knowledge_service.get_all_knowledge_in_brain( - brain_1.brain_id - ) - assert len(all_km) == 1 - created_km = all_km[0] - assert created_km.file_name == sync_file.name - assert created_km.extension == ".txt" - assert created_km.file_sha1 is None - assert created_km.created_at is not None - assert created_km.metadata == {"sync_file_id": "1"} - assert len(created_km.brains) > 0 - assert created_km.brains[0]["brain_id"] == brain_1.brain_id - - # Assert celery task in correct - assert task["args"] == ("process_file_task",) - minimal_task_kwargs = { - "brain_id": brain_1.brain_id, - "knowledge_id": created_km.id, - "file_original_name": sync_file.name, - "source": syncutils.sync_cloud.name, - "notification_id": sync_file.notification_id, - } - all( - minimal_task_kwargs[key] == task["kwargs"][key] # type: ignore - for key in minimal_task_kwargs - ) - - -@pytest.mark.skip( - reason="Bug: UnboundLocalError: cannot access local variable 'response'" -) -@pytest.mark.asyncio(loop_scope="session") -async def test_process_sync_file_with_prev( - monkeypatch, - supabase_client, - brain_user_setup: Tuple[Brain, User], - setup_syncs_data: Tuple[SyncsUser, SyncsActive], - syncutils: SyncUtils, - sync_file: SyncFile, - prev_file: SyncFile, -): - task = {} - - def _send_task(*args, **kwargs): - task["args"] = args - task["kwargs"] = {**kwargs["kwargs"]} - - monkeypatch.setattr("quivr_api.celery_config.celery.send_task", _send_task) - brain_1, _ = brain_user_setup - assert brain_1.brain_id - (sync_user, sync_active) = setup_syncs_data - - # Run process_file on prev_file first - await syncutils.process_sync_file( - file=prev_file, - previous_file=None, - current_user=sync_user, - sync_active=sync_active, - ) - dbfiles: list[DBSyncFile] = syncutils.sync_files_repo.get_sync_files(sync_active.id) - assert len(dbfiles) == 1 - prev_dbfile = dbfiles[0] - - assert check_file_exists(str(brain_1.brain_id), prev_file.name) - prev_file_data = supabase_client.storage.from_("quivr").download( - f"{brain_1.brain_id}/{prev_file.name}" - ) - - ##### - # Run process_file on newer file - await syncutils.process_sync_file( - file=sync_file, - previous_file=prev_dbfile, - current_user=sync_user, - sync_active=sync_active, - ) - - # Check notification inserted - assert ( - sync_file.notification_id in syncutils.notification_service.repository.received # type: ignore - ) - assert ( - syncutils.notification_service.repository.received[ # type: ignore - sync_file.notification_id # type: ignore - ].status - == NotificationsStatusEnum.SUCCESS - ) - - # Check Syncfile created - dbfiles: list[DBSyncFile] = syncutils.sync_files_repo.get_sync_files(sync_active.id) - assert len(dbfiles) == 1 - assert dbfiles[0].brain_id == str(brain_1.brain_id) - assert dbfiles[0].syncs_active_id == sync_active.id - assert dbfiles[0].supported - - # Check prev file was deleted and replaced with the new - all_km = await syncutils.knowledge_service.get_all_knowledge_in_brain( - brain_1.brain_id - ) - assert len(all_km) == 1 - created_km = all_km[0] - assert created_km.file_name == sync_file.name - assert created_km.extension == ".txt" - assert created_km.file_sha1 is None - assert created_km.updated_at - assert created_km.created_at - assert created_km.updated_at == created_km.created_at # new line - assert created_km.metadata == {"sync_file_id": str(dbfiles[0].id)} - assert created_km.brains[0]["brain_id"] == brain_1.brain_id - - # Check file content changed - assert check_file_exists(str(brain_1.brain_id), sync_file.name) - new_file_data = supabase_client.storage.from_("quivr").download( - f"{brain_1.brain_id}/{sync_file.name}" - ) - assert new_file_data != prev_file_data, "Same file in prev_file and new file" - - # Assert celery task in correct - assert task["args"] == ("process_file_task",) - minimal_task_kwargs = { - "brain_id": brain_1.brain_id, - "knowledge_id": created_km.id, - "file_original_name": sync_file.name, - "source": syncutils.sync_cloud.name, - "notification_id": sync_file.notification_id, - } - all( - minimal_task_kwargs[key] == task["kwargs"][key] # type: ignore - for key in minimal_task_kwargs - ) diff --git a/backend/api/quivr_api/modules/sync/utils/LICENSE b/backend/api/quivr_api/modules/sync/utils/LICENSE deleted file mode 100644 index 958f43707..000000000 --- a/backend/api/quivr_api/modules/sync/utils/LICENSE +++ /dev/null @@ -1,36 +0,0 @@ -The Quivr Enterprise license (the “Enterprise Licenseâ€) -Copyright (c) 2023-2024 Quivr Technologies Inc. - -With regard to the Quivr Software: - -This software and associated documentation files (the "Software") may only be -used in production, if you (and any entity that you represent) have agreed to, -and are in compliance with, the Quivr Subscription Terms of Service, available -at https://quivr.com/pricing (the “Enterprise Termsâ€), or other -agreement governing the use of the Software, as agreed by you and Quivr, -and otherwise have a valid Quivr Enterprise license for the -correct number of user seats. Subject to the foregoing sentence, you are free to -modify this Software and publish patches to the Software. You agree that Quivr -and/or its licensors (as applicable) retain all right, title and interest in and -to all such modifications and/or patches, and all such modifications and/or -patches may only be used, copied, modified, displayed, distributed, or otherwise -exploited with a valid Quivr Enterprise license for the correct -number of user seats. Notwithstanding the foregoing, you may copy and modify -the Software for development and testing purposes, without requiring a -subscription. You agree that Quivr and/or its licensors (as applicable) retain -all right, title and interest in and to all such modifications. You are not -granted any other rights beyond what is expressly stated herein. Subject to the -foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, -and/or sell the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -For all third party components incorporated into the Quivr Software, those -components are licensed under the original license provided by the owner of the -applicable component. diff --git a/backend/api/quivr_api/modules/sync/utils/__init__.py b/backend/api/quivr_api/modules/sync/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/utils/githubutils.py b/backend/api/quivr_api/modules/sync/utils/githubutils.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/sync/utils/normalize.py b/backend/api/quivr_api/modules/sync/utils/normalize.py deleted file mode 100644 index 3f8042c9c..000000000 --- a/backend/api/quivr_api/modules/sync/utils/normalize.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -import re -import unicodedata - -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - - -def remove_special_characters(input): - try: - normalized_string = unicodedata.normalize("NFD", input) - normalized_string = re.sub(r"[^\w\s.]", "", normalized_string) - logger.info(f"Input: {input}, Normalized: {normalized_string}") - return normalized_string - except Exception as e: - logger.error(f"Error removing special characters: {e}") - return input - - -def sanitize_filename(filename: str) -> str: - """ - Sanitize the filename to make it usable. - - Args: - filename (str): The original filename. - - Returns: - str: The sanitized filename. - - This function: - 1. Removes or replaces invalid characters - 2. Handles double extensions - 3. Ensures the filename is not empty - 4. Truncates long filenames - """ - valid_chars = re.sub(r"[^\w\-_\. ]", "", filename) - - name, ext = os.path.splitext(valid_chars) - - name = name.replace(".", "_") - - if not name: - name = "unnamed" - max_length = 255 - len(ext) - if len(name) > max_length: - name = name[:max_length] - sanitized_filename = f"{name}{ext}" - - return sanitized_filename diff --git a/backend/api/quivr_api/modules/sync/utils/sync.py b/backend/api/quivr_api/modules/sync/utils/sync.py deleted file mode 100644 index a60ccb0aa..000000000 --- a/backend/api/quivr_api/modules/sync/utils/sync.py +++ /dev/null @@ -1,1220 +0,0 @@ -import asyncio -import json -import os -import time -from abc import ABC, abstractmethod -from datetime import datetime -from io import BytesIO -from typing import Any, Dict, List, Optional, Union - -import dropbox -import markdownify -import msal -import requests # type: ignore -from fastapi import HTTPException -from google.auth.transport.requests import Request as GoogleRequest -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build -from notion_client import Client -from requests import HTTPError - -from quivr_api.logger import get_logger -from quivr_api.modules.sync.entity.sync_models import SyncFile -from quivr_api.modules.sync.service.sync_notion import SyncNotionService -from quivr_api.modules.sync.utils.normalize import remove_special_characters - -logger = get_logger(__name__) - - -class BaseSync(ABC): - name: str - lower_name: str - datetime_format: str - - @abstractmethod - def get_files_by_id(self, credentials: Dict, file_ids: List[str]) -> List[SyncFile]: - raise NotImplementedError - - @abstractmethod - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - pass - - @abstractmethod - def get_files( - self, credentials: Dict, folder_id: str | None = None, recursive: bool = False - ) -> List[SyncFile]: - raise NotImplementedError - - @abstractmethod - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - pass - - @abstractmethod - def check_and_refresh_access_token(self, credentials: dict) -> Dict: - raise NotImplementedError - - @abstractmethod - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - raise NotImplementedError - - @abstractmethod - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - pass - - -class GoogleDriveSync(BaseSync): - name = "Google Drive" - lower_name = "google" - creds: Credentials | None = None - service: Any | None = None - datetime_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" - - def check_and_refresh_access_token(self, credentials: dict) -> Dict: - self.creds = Credentials.from_authorized_user_info(credentials) - if self.creds.expired and self.creds.refresh_token: - self.creds.refresh(GoogleRequest()) - logger.info("Google Drive credentials refreshed") - return json.loads(self.creds.to_json()) - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - file_id = file.id - file_name = file.name - mime_type = file.mime_type - if not self.creds: - self.check_and_refresh_access_token(credentials) - if not self.service: - self.service = build("drive", "v3", credentials=self.creds) - - # Convert Google Docs files to appropriate formats before downloading - if mime_type == "application/vnd.google-apps.document": - logger.info( - "Converting Google Docs file with file_id: %s to DOCX.", - file_id, - ) - request = self.service.files().export_media( - fileId=file_id, - mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ) - file_name += ".docx" - elif mime_type == "application/vnd.google-apps.spreadsheet": - logger.info( - "Converting Google Sheets file with file_id: %s to XLSX.", - file_id, - ) - request = self.service.files().export_media( - fileId=file_id, - mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ) - file_name += ".xlsx" - elif mime_type == "application/vnd.google-apps.presentation": - logger.info( - "Converting Google Slides file with file_id: %s to PPTX.", - file_id, - ) - request = self.service.files().export_media( - fileId=file_id, - mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation", - ) - file_name += ".pptx" - ### Elif pdf, txt, md, csv, docx, xlsx, pptx, doc - elif file_name.split(".")[-1] in [ - "pdf", - "txt", - "md", - "csv", - "docx", - "xlsx", - "pptx", - "doc", - ]: - request = self.service.files().get_media(fileId=file_id) - else: - logger.warning( - "Skipping unsupported file type: %s for file_id: %s", - mime_type, - file_id, - ) - raise Exception("Unsupported file type") - - file_data = request.execute() - return {"file_name": file_name, "content": BytesIO(file_data)} - - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - return self.download_file(credentials, file) - - def get_files_by_id(self, credentials: Dict, file_ids: List[str]) -> List[SyncFile]: - """ - Retrieve files from Google Drive by their IDs. - - Args: - credentials (dict): The credentials for accessing Google Drive. - file_ids (list): The list of file IDs to retrieve. - - Returns: - list: A list of dictionaries containing the metadata of each file or an error message. - """ - logger.info("Retrieving Google Drive files with file_ids: %s", file_ids) - self.check_and_refresh_access_token(credentials) - - try: - service = build("drive", "v3", credentials=self.creds) - files: List[SyncFile] = [] - - for file_id in file_ids: - result = ( - service.files() - .get( - fileId=file_id, - fields="id, name, mimeType, modifiedTime, webViewLink, size", - ) - .execute() - ) - - files.append( - SyncFile( - name=result["name"], - id=result["id"], - is_folder=( - result["mimeType"] == "application/vnd.google-apps.folder" - ), - last_modified=result["modifiedTime"], - mime_type=result["mimeType"], - web_view_link=result["webViewLink"], - size=result.get("size", None), - ) - ) - - logger.info("Google Drive files retrieved successfully: %s", len(files)) - for file in files: - file.name = remove_special_characters(file.name) - return files - - except HTTPError as error: - logger.error( - "An error occurred while retrieving Google Drive files: %s", error - ) - raise Exception("Failed to retrieve files") - - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - return self.get_files_by_id(credentials, file_ids) - - def get_files( - self, credentials: dict, folder_id: str | None = None, recursive: bool = False - ) -> List[SyncFile]: - """ - Retrieve files from Google Drive. - - Args: - credentials (dict): The credentials for accessing Google Drive. - folder_id (str, optional): The folder ID to filter files. Defaults to None. - recursive (bool, optional): If True, fetch files from all subfolders. Defaults to False. - - Returns: - dict: A dictionary containing the list of files or an error message. - """ - logger.info("Retrieving Google Drive files with folder_id: %s", folder_id) - - self.check_and_refresh_access_token(credentials) - # Updating the credentials in the database - - try: - service = build("drive", "v3", credentials=self.creds) - if folder_id: - query = f"'{folder_id}' in parents" - else: - query = "'root' in parents or sharedWithMe" - page_token = None - files: List[SyncFile] = [] - - while True: - results = ( - service.files() - .list( - q=query, - pageSize=100, - fields="nextPageToken, files(id, name, mimeType, modifiedTime, webViewLink, size)", - pageToken=page_token, - ) - .execute() - ) - items = results.get("files", []) - - if not items: - logger.info("No files found in Google Drive") - break - - for item in items: - files.append( - SyncFile( - name=item["name"], - id=item["id"], - is_folder=( - item["mimeType"] == "application/vnd.google-apps.folder" - ), - last_modified=item["modifiedTime"], - mime_type=item["mimeType"], - web_view_link=item["webViewLink"], - size=item.get("size", None), - ) - ) - - # If recursive is True and the item is a folder, get files from the folder - if ( - recursive - and item["mimeType"] == "application/vnd.google-apps.folder" - ): - logger.warning( - "Calling Recursive for folder: %s", - item["name"], - ) - files.extend(self.get_files(credentials, item["id"], recursive)) - - page_token = results.get("nextPageToken", None) - if page_token is None: - break - - logger.info("Google Drive files retrieved successfully: %s", len(files)) - - for file in files: - file.name = remove_special_characters(file.name) - return files - except HTTPError as error: - logger.error( - "An error occurred while retrieving Google Drive files: %s", error - ) - raise Exception("Failed to retrieve files") - - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - return self.get_files(credentials, folder_id, recursive) - - -class AzureDriveSync(BaseSync): - name = "Share Point" - lower_name = "azure" - datetime_format: str = "%Y-%m-%dT%H:%M:%SZ" - CLIENT_ID = os.getenv("SHAREPOINT_CLIENT_ID") - CLIENT_SECRET = os.getenv("SHAREPOINT_CLIENT_SECRET") - AUTHORITY = "https://login.microsoftonline.com/common" - BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") - REDIRECT_URI = f"{BACKEND_URL}/sync/azure/oauth2callback" - SCOPE = [ - "https://graph.microsoft.com/Files.Read", - "https://graph.microsoft.com/User.Read", - "https://graph.microsoft.com/Sites.Read.All", - ] - - @staticmethod - def get_azure_token_data(credentials): - if "access_token" not in credentials: - raise HTTPException(status_code=401, detail="Invalid token data") - return credentials - - @staticmethod - def get_azure_headers(token_data): - return { - "Authorization": f"Bearer {token_data['access_token']}", - "Accept": "application/json", - } - - def check_and_refresh_access_token(self, credentials) -> Dict: - if "refresh_token" not in credentials: - raise HTTPException(status_code=401, detail="No refresh token available") - - client = msal.ConfidentialClientApplication( - self.CLIENT_ID, - authority=self.AUTHORITY, - client_credential=self.CLIENT_SECRET, - ) - result = client.acquire_token_by_refresh_token( - credentials["refresh_token"], scopes=self.SCOPE - ) - if "access_token" not in result: - raise HTTPException( - status_code=400, detail=f"Failed to refresh token: {result}" - ) - - credentials.update( - { - "access_token": result["access_token"], - "refresh_token": result.get( - "refresh_token", credentials["refresh_token"] - ), - "id_token": result.get("id_token", credentials.get("id_token")), - } - ) - - return credentials - - def get_files( - self, credentials, site_folder_id=None, recursive=False - ) -> List[SyncFile]: - def fetch_files(endpoint, headers, max_retries=1): - logger.debug(f"fetching files from {endpoint}.") - - retry_count = 0 - while retry_count <= max_retries: - try: - response = requests.get(endpoint, headers=headers) - - # Retrying with refereshed token - if response.status_code == 401: - token_data = self.check_and_refresh_access_token(credentials) - headers = self.get_azure_headers(token_data) - response = requests.get(endpoint, headers=headers) - else: - response.raise_for_status() - return response.json().get("value", []) - - except HTTPError as e: - logger.exception( - f"azure_list_files got exception : {e}. headers: {headers}. {retry_count} retrying." - ) - # Exponential backoff - time.sleep(2**retry_count) - retry_count += 1 - - raise HTTPException( - 504, detail="can't connect to azure endpoint to retrieve files." - ) - - token_data = self.get_azure_token_data(credentials) - headers = self.get_azure_headers(token_data) - if not site_folder_id: - folder_id = None - site_id = None - else: - site_id, folder_id = site_folder_id.split(":") - if folder_id == "": - folder_id = None - - if not folder_id and not site_id: - # Fetch the sites - # endpoint = "https://graph.microsoft.com/v1.0/me/followedSites" - endpoint = "https://graph.microsoft.com/v1.0/sites?search=*" - elif site_id == "root": - if not folder_id: - endpoint = "https://graph.microsoft.com/v1.0/me/drive/root/children" - else: - endpoint = f"https://graph.microsoft.com/v1.0/me/drive/items/{folder_id}/children" - else: - if not folder_id: - endpoint = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drive/root/children" - else: - endpoint = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drive/items/{folder_id}/children" - items = fetch_files(endpoint, headers) - - if not folder_id and not site_id and len(items) == 0: - logger.debug("No sites found in Azure Drive, files : %s", items) - return self.get_files(credentials, "root:", recursive) - - if not items: - logger.info("No files found in Azure Drive") - return [] - else: - logger.info("Azure Drive files found: %s", items) - - files = [] - for item in items: - file_data = SyncFile( - name=item.get("name") if site_folder_id else item.get("displayName"), - id=( - f'{item.get("id", {}).split(",")[1]}:' - if not site_folder_id - else f'{site_id}:{item.get("id")}' - ), - is_folder="folder" in item or not site_folder_id, - last_modified=item.get("lastModifiedDateTime"), - mime_type=item.get("file", {}).get("mimeType", "folder"), - web_view_link=item.get("webUrl"), - size=item.get("size", None), - ) - files.append(file_data) - - # If recursive option is enabled and the item is a folder, fetch files from it - if recursive and file_data.is_folder: - folder_files = self.get_files( - credentials, site_folder_id=file_data.id, recursive=True - ) - - files.extend(folder_files) - if not folder_id and not site_id: - files.append( - SyncFile( - name="My Drive", - id="root:", - is_folder=True, - last_modified="", - mime_type="folder", - web_view_link="https://onedrive.live.com", - ) - ) - for file in files: - file.name = remove_special_characters(file.name) - logger.info("Azure Drive files retrieved successfully: %s", len(files)) - return files - - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - return self.get_files(credentials, folder_id, recursive) - - def get_files_by_id(self, credentials: dict, file_ids: List[str]) -> List[SyncFile]: - """ - Retrieve files from Azure Drive by their IDs. - - Args: - credentials (dict): The credentials for accessing Azure Drive. - file_ids (list): The list of file IDs to retrieve. - - Returns: - list: A list of dictionaries containing the metadata of each file or an error message. - """ - logger.info("Retrieving Azure Drive files with file_ids: %s", file_ids) - token_data = self.get_azure_token_data(credentials) - headers = self.get_azure_headers(token_data) - files = [] - - for file_id in file_ids: - site_id, folder_id = file_id.split(":") - if folder_id == "": - folder_id = None - if site_id == "root": - endpoint = ( - f"https://graph.microsoft.com/v1.0/me/drive/items/{folder_id}" - ) - else: - endpoint = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drive/items/{folder_id}" - response = requests.get(endpoint, headers=headers) - if response.status_code == 401: - token_data = self.check_and_refresh_access_token(credentials) - headers = self.get_azure_headers(token_data) - response = requests.get(endpoint, headers=headers) - if response.status_code != 200: - logger.error( - "An error occurred while retrieving Azure Drive files: %s", - response.text, - ) - raise Exception("Failed to retrieve files") - - result = response.json() - files.append( - SyncFile( - name=result.get("name"), - id=f'{site_id}:{result.get("id")}', - is_folder="folder" in result, - last_modified=result.get("lastModifiedDateTime"), - mime_type=result.get("file", {}).get("mimeType", "folder"), - web_view_link=result.get("webUrl"), - size=result.get("size", None), - ) - ) - - for file in files: - file.name = remove_special_characters(file.name) - logger.info("Azure Drive files retrieved successfully: %s", len(files)) - return files - - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - return self.get_files_by_id(credentials, file_ids) - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - file_id = file.id - file_name = file.name - headers = self.get_azure_headers(credentials) - site_id, folder_id = file_id.split(":") - if folder_id == "": - folder_id = None - if site_id == "root": - download_endpoint = ( - f"https://graph.microsoft.com/v1.0/me/drive/items/{file_id}/content" - ) - else: - download_endpoint = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drive/items/{folder_id}/content" - logger.info("Downloading file: %s", file_name) - download_response = requests.get( - download_endpoint, headers=headers, stream=True - ) - return {"file_name": file_name, "content": BytesIO(download_response.content)} - - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - return self.download_file(credentials, file) - - -class DropboxSync(BaseSync): - name = "Dropbox" - lower_name = "dropbox" - dbx: dropbox.Dropbox | None = None - datetime_format: str = "%Y-%m-%d %H:%M:%S" - - def link_dropbox(self, credentials) -> dropbox.Dropbox: - return dropbox.Dropbox( - credentials["access_token"], - oauth2_refresh_token=credentials["refresh_token"], - app_key=os.getenv("DROPBOX_APP_KEY"), - oauth2_access_token_expiration=credentials.get("expires_at"), - app_secret=os.getenv("DROPBOX_APP_SECRET"), - ) - - def check_and_refresh_access_token(self, credentials: Dict) -> Dict: - if not self.dbx: - self.dbx = self.link_dropbox(credentials) - self.dbx.check_and_refresh_access_token() - credentials["access_token"] = self.dbx._oauth2_access_token - credentials["refresh_token"] = self.dbx.refresh_access_token - return credentials - - def get_files( - self, credentials: Dict, folder_id: str | None = "", recursive: bool = False - ) -> List[SyncFile]: - """ - Retrieve files from Dropbox. - - Args: - credentials (dict): The credentials for accessing Dropbox. - folder_id (str, optional): The folder ID to filter files. Defaults to "". - recursive (bool, optional): If True, fetch files from all subfolders. Defaults to False. - - Returns: - dict: A dictionary containing the list of files or an error message. - """ - logger.info("Retrieving Dropbox files with folder_id: %s", folder_id) - - # Verify credential has the access token - if "access_token" not in credentials: - logger.error("Invalid access token") - raise Exception("Invalid access token") - - try: - if not self.dbx: - self.dbx = dropbox.Dropbox( - credentials["access_token"], - oauth2_refresh_token=credentials["refresh_token"], - app_key=os.getenv("DROPBOX_APP_KEY"), - oauth2_access_token_expiration=credentials.get("expires_at"), - app_secret=os.getenv("DROPBOX_APP_SECRET"), - ) - self.dbx.check_and_refresh_access_token() - credentials["access_token"] = self.dbx._oauth2_access_token - - def fetch_files(metadata): - files = [] - for file in metadata.entries: - shared_link = f"https://www.dropbox.com/preview{file.path_display}?context=content_suggestions&role=personal" - is_folder = isinstance(file, dropbox.files.FolderMetadata) - - files.append( - SyncFile( - name=file.name, - id=file.id, - is_folder=is_folder, - last_modified=( - str(file.client_modified) if not is_folder else "" - ), - mime_type=( - file.path_lower.split(".")[-1] if not is_folder else "" - ), - web_view_link=shared_link, - ) - ) - return files - - files = [] - list_metadata = self.dbx.files_list_folder(folder_id, recursive=recursive) - files.extend(fetch_files(list_metadata)) - - while list_metadata.has_more: - list_metadata = self.dbx.files_list_folder_continue( - list_metadata.cursor - ) - files.extend(fetch_files(list_metadata)) - - for file in files: - file.name = remove_special_characters(file.name) - - logger.info("Dropbox files retrieved successfully: %d", len(files)) - return files - - except dropbox.exceptions.ApiError as e: - logger.error("Dropbox API error: %s", e) - raise Exception("Failed to retrieve files") - except Exception as e: - logger.error("Unexpected error: %s", e) - raise Exception("Failed to retrieve files") - - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - return self.get_files(credentials, folder_id, recursive) - - def get_files_by_id( - self, credentials: Dict[str, str], file_ids: List[str] - ) -> List[SyncFile]: - """ - Retrieve files from Dropbox by their IDs. - - Args: - credentials (dict): The credentials for accessing Dropbox. - file_ids (list): The list of file IDs to retrieve. - - Returns: - list: A list of dictionaries containing the metadata of each file or an error message. - """ - logger.info("Retrieving Dropbox files with file_ids: %s", file_ids) - - if "access_token" not in credentials: - logger.error("Access token is not in the credentials") - raise Exception("Invalid access token") - - try: - if not self.dbx: - self.dbx = self.link_dropbox(credentials) - self.dbx.check_and_refresh_access_token() - credentials["access_token"] = self.dbx._oauth2_access_token # type: ignore - - files = [] - - for file_id in file_ids: - try: - metadata = self.dbx.files_get_metadata(file_id) - shared_link = f"https://www.dropbox.com/preview/{metadata.path_display}?context=content_suggestions&role=personal" - is_folder = isinstance(metadata, dropbox.files.FolderMetadata) - file_info = SyncFile( - name=metadata.name, - id=metadata.id, - is_folder=is_folder, - last_modified=( - str(metadata.client_modified) if not is_folder else "" - ), - mime_type=( - metadata.path_lower.split(".")[-1] if not is_folder else "" - ), - web_view_link=shared_link, - size=metadata.size, - ) - - files.append(file_info) - except dropbox.exceptions.ApiError as api_err: - logger.error( - "Dropbox API error for file_id %s: %s", file_id, api_err - ) - continue # Skip this file and proceed with the next one - except Exception as err: - logger.error("Unexpected error for file_id %s: %s", file_id, err) - continue # Skip this file and proceed with the next one - - for file in files: - file.name = remove_special_characters(file.name) - - logger.info("Dropbox files retrieved successfully: %d", len(files)) - return files - - except dropbox.exceptions.AuthError as auth_err: - logger.error("Authentication error: %s", auth_err) - raise Exception("Failed to retrieve files") - except Exception as e: - logger.error("Unexpected error: %s", e) - raise Exception("Failed to retrieve files") - - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - return self.get_files_by_id(credentials, file_ids) - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - file_id = str(file.id) - file_name = file.name - if not self.dbx: - self.dbx = self.link_dropbox(credentials) - - metadata, file_data = self.dbx.files_download(file_id) # type: ignore - return {"file_name": file_name, "content": BytesIO(file_data.content)} - - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - return self.download_file(credentials, file) - - -class NotionSync(BaseSync): - name = "Notion" - lower_name = "notion" - notion: Optional[Client] = None - datetime_format: str = "%Y-%m-%d %H:%M:%S%z" - notion_service: SyncNotionService - - def __init__(self, notion_service: SyncNotionService): - self.notion_service = notion_service - super().__init__() - - def link_notion(self, credentials) -> Client: - return Client(auth=credentials["access_token"]) - - def check_and_refresh_access_token(self, credentials: Dict) -> Dict: - if not self.notion: - self.notion = self.link_notion(credentials) - # no need to refresh token for notion - return credentials - - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - assert sync_user_id, "should not be optional for notion" - pages = [] - - if not self.notion: - self.link_notion(credentials) - - if not folder_id or folder_id == "": - folder_id = None # ROOT FOLDER HAVE A TRUE PARENT ID - - children = await self.notion_service.get_notion_files_by_parent_id( - folder_id, sync_user_id - ) - for page in children: - page_info = SyncFile( - name=page.name, - id=str(page.notion_id), - is_folder=await self.notion_service.is_folder_page(page.notion_id), - last_modified=str(page.last_modified), - mime_type=page.mime_type, - web_view_link=page.web_view_link, - icon=page.icon, - ) - - pages.append(page_info) - - if recursive: - sub_pages = await self.aget_files( - credentials=credentials, - sync_user_id=sync_user_id, - folder_id=str(page.id), - recursive=recursive, - ) - pages.extend(sub_pages) - return pages - - def get_files( - self, credentials: Dict, folder_id: str | None = None, recursive: bool = False - ) -> List[SyncFile]: - loop = asyncio.get_event_loop() - result = loop.run_until_complete( - self.aget_files(credentials, folder_id, recursive) - ) - - loop.close() - - return result - - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - logger.info("Retrieving Notion files by file_ids: %s", file_ids) - files = [] - pages = await self.notion_service.get_notion_files_by_ids(file_ids) - - for page in pages: - try: - page_info = SyncFile( - name=page.name, - id=str(page.notion_id), - is_folder=await self.notion_service.is_folder_page(page.notion_id), - last_modified=str(page.last_modified), - mime_type=page.mime_type, - web_view_link=page.web_view_link, - icon=page.icon, - ) - files.append(page_info) - - except Exception as e: - logger.error("Error retrieving Notion file with ID %s: %s", page.id, e) - continue # Skip this file and proceed with the next one - - logger.info("Notion files retrieved successfully by IDs: %d", len(files)) - return files - - def get_files_by_id(self, credentials: Dict, file_ids: List[str]) -> List[SyncFile]: - loop = asyncio.get_event_loop() - result = loop.run_until_complete(self.aget_files_by_id(credentials, file_ids)) - loop.close() - return result - - def get_block_content(self, block): - block_type = block["type"] - result = "" - - if block_type == "image": - return "![Image](%s)" % block["image"]["file"]["url"] - if "rich_text" not in block[block_type]: - if "title" not in block[block_type]: - return "--- ---" - return f'{block[block_type]["title"]} {": database" if block_type == "child_database" else ": linked page"}' - - if len(block[block_type]["rich_text"]) == 0: - return "" - - if block_type == "paragraph": - result = markdownify.markdownify( - block["paragraph"]["rich_text"][0]["plain_text"] - ) - - elif block_type == "heading_1": - result = "# " + markdownify.markdownify( - block["heading_1"]["rich_text"][0]["plain_text"] - ) - - elif block_type == "heading_2": - result = "## " + markdownify.markdownify( - block["heading_2"]["rich_text"][0]["plain_text"] - ) - elif block_type == "heading_3": - result = "### " + markdownify.markdownify( - block["heading_3"]["rich_text"][0]["plain_text"] - ) - elif block_type == "bulleted_list_item": - result = "* " + markdownify.markdownify( - block["bulleted_list_item"]["rich_text"][0]["plain_text"] - ) - - elif block_type == "numbered_list_item": - result = "1. " + markdownify.markdownify( - block["numbered_list_item"]["rich_text"][0]["plain_text"] - ) - elif block_type == "to_do": - checked = "x" if block["to_do"]["checked"] else " " - result = f"- [{checked}] " + markdownify.markdownify( - block["to_do"]["rich_text"][0]["plain_text"] - ) - elif block_type == "toggle": - result = "> " + markdownify.markdownify( - block["toggle"]["rich_text"][0]["plain_text"] - ) - - elif block_type == "quote": - result = "> " + markdownify.markdownify( - block["quote"]["rich_text"][0]["plain_text"] - ) - elif block_type == "code": - result = ( - "```" - + block["code"]["language"] - + "\n" - + markdownify.markdownify(block["code"]["rich_text"][0]["plain_text"]) - + "\n```" - ) - - elif block_type == "callout": - result = "> " + markdownify.markdownify( - block["callout"]["rich_text"][0]["plain_text"] - ) - else: - result = markdownify.markdownify( - block[block_type]["rich_text"][0]["plain_text"] - ) - - return result - - async def adownload_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - if not self.notion: - self.notion = self.link_notion(credentials) - - logger.info("Downloading Notion file (page) with ID %s", file.id) - - try: - - async def retrieve_page_content(page_id) -> List[str]: - blocks = self.notion.blocks.children.list(page_id) # type: ignore - - blocks = blocks["results"] # type: ignore - if not blocks: - raise Exception("Page does not exist") - - markdown_content = [] - for block in blocks: - logger.info(f"Block: {block}") - if "image" in block["type"] or "file" in block["type"]: - logger.info(f"Block is an image or file: {block}") - continue - markdown_content.append(self.get_block_content(block)) - if block["has_children"]: - sub_elements = [ - f"\t{content}" - for content in await retrieve_page_content(block["id"]) - ] - markdown_content.extend(sub_elements) - return markdown_content - - markdown_content = await retrieve_page_content(file.id) - markdown_text = "\n\n".join(markdown_content) - - markdown_bytes = BytesIO(markdown_text.encode("utf-8")) - - return {"file_name": f"{file.name}", "content": markdown_bytes} - - except Exception as e: - logger.error( - "Error downloading Notion file (page) with ID %s: %s", file.id, e - ) - raise Exception("Failed to download file") - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.adownload_file(credentials, file)) - - -class GitHubSync(BaseSync): - name = "GitHub" - lower_name = "github" - datetime_format = "%Y-%m-%dT%H:%M:%SZ" - - def __init__(self): - self.CLIENT_ID = os.getenv("GITHUB_CLIENT_ID") - self.CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET") - self.BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:5050") - self.REDIRECT_URI = f"{self.BACKEND_URL}/sync/github/oauth2callback" - self.SCOPE = "repo user" - - def get_github_token_data(self, credentials): - if "access_token" not in credentials: - raise HTTPException(status_code=401, detail="Invalid token data") - return credentials - - def get_github_headers(self, token_data): - return { - "Authorization": f"Bearer {token_data['access_token']}", - "Accept": "application/json", - } - - def check_and_refresh_access_token(self, credentials: dict) -> Dict: - # GitHub tokens do not support refresh token, usually need to re-authenticate - raise HTTPException( - status_code=400, detail="GitHub does not support token refresh" - ) - - def get_files( - self, credentials: Dict, folder_id: str | None = None, recursive: bool = False - ) -> List[SyncFile]: - logger.info("Retrieving GitHub files with folder_id: %s", folder_id) - if folder_id: - return self.list_github_files_in_repo( - credentials, folder_id, recursive=recursive - ) - else: - return self.list_github_repos(credentials, recursive=recursive) - - async def aget_files( - self, - credentials: Dict, - folder_id: str | None = None, - recursive: bool = False, - sync_user_id: int | None = None, - ) -> List[SyncFile]: - return self.get_files(credentials, folder_id, recursive) - - def get_files_by_id(self, credentials: Dict, file_ids: List[str]) -> List[SyncFile]: - token_data = self.get_github_token_data(credentials) - headers = self.get_github_headers(token_data) - files = [] - - for file_id in file_ids: - repo_name, file_path = file_id.split(":") - endpoint = f" https://api.github.com/repos/{repo_name}/contents/{file_path}" - response = requests.get(endpoint, headers=headers) - if response.status_code == 401: - raise HTTPException(status_code=401, detail="Unauthorized") - if response.status_code != 200: - logger.error( - "An error occurred while retrieving GitHub files: %s", response.text - ) - raise Exception("Failed to retrieve files") - - result = response.json() - logger.debug("GitHub file result: %s", result) - files.append( - SyncFile( - name=remove_special_characters(result.get("name")), - id=f"{repo_name}:{result.get('path')}", - is_folder=False, - last_modified=datetime.now().strftime(self.datetime_format), - mime_type=result.get("type"), - web_view_link=result.get("html_url"), - size=result.get("size", None), - ) - ) - - logger.info("GitHub files retrieved successfully: %s", len(files)) - return files - - async def aget_files_by_id( - self, credentials: Dict, file_ids: List[str] - ) -> List[SyncFile]: - return self.get_files_by_id(credentials, file_ids) - - def download_file( - self, credentials: Dict, file: SyncFile - ) -> Dict[str, Union[str, BytesIO]]: - token_data = self.get_github_token_data(credentials) - headers = self.get_github_headers(token_data) - project_name, file_path = file.id.split(":") - - # Construct the API endpoint for the file content - endpoint = f"https://api.github.com/repos/{project_name}/contents/{file_path}" - - response = requests.get(endpoint, headers=headers) - if response.status_code != 200: - raise HTTPException( - status_code=response.status_code, detail="Failed to download file" - ) - - content = response.json().get("content") - if not content: - raise HTTPException(status_code=404, detail="File content not found") - - # GitHub API returns content as base64 encoded string - import base64 - - file_content = base64.b64decode(content) - - return {"file_name": file.name, "content": BytesIO(file_content)} - - async def adownload_file(self, credentials: Dict, file: SyncFile): - return self.download_file(credentials, file) - - def list_github_repos(self, credentials, recursive=False): - def fetch_repos(endpoint, headers): - response = requests.get(endpoint, headers=headers) - if response.status_code == 401: - raise HTTPException(status_code=401, detail="Unauthorized") - if response.status_code != 200: - logger.error( - "An error occurred while retrieving GitHub repositories: %s", - response.text, - ) - return [] - return response.json() - - token_data = self.get_github_token_data(credentials) - headers = self.get_github_headers(token_data) - endpoint = "https://api.github.com/user/repos" - - items = fetch_repos(endpoint, headers) - - if not items: - logger.info("No repositories found in GitHub") - return [] - - repos = [] - for item in items: - repo_data = SyncFile( - name=remove_special_characters(item.get("name")), - id=f"{item.get('full_name')}:", - is_folder=True, - last_modified=str(item.get("updated_at")), - mime_type="repository", - web_view_link=item.get("html_url"), - size=item.get("size", None), - ) - repos.append(repo_data) - - if recursive: - submodule_files = self.list_github_files_in_repo( - credentials, repo_data.id - ) - repos.extend(submodule_files) - - logger.info("GitHub repositories retrieved successfully: %s", len(repos)) - return repos - - def list_github_files_in_repo(self, credentials, repo_folder, recursive=False): - def fetch_files(endpoint, headers): - response = requests.get(endpoint, headers=headers) - if response.status_code == 401: - raise HTTPException(status_code=401, detail="Unauthorized") - if response.status_code != 200: - logger.error( - "An error occurred while retrieving GitHub repository files: %s", - response.text, - ) - return [] - return response.json() - - repo_name, folder_path = repo_folder.split(":") - token_data = self.get_github_token_data(credentials) - headers = self.get_github_headers(token_data) - endpoint = f"https://api.github.com/repos/{repo_name}/contents/{folder_path}" - logger.debug(f"Fetching files from GitHub with link: {endpoint}") - - items = fetch_files(endpoint, headers) - - if not items: - logger.info(f"No files found in GitHub repository {repo_name}") - return [] - - files = [] - for item in items: - file_data = SyncFile( - name=remove_special_characters(item.get("name")), - id=f"{repo_name}:{item.get('path')}", - is_folder=item.get("type") == "dir", - last_modified=str(item.get("updated_at")), - mime_type=item.get("type"), - web_view_link=item.get("html_url"), - size=item.get("size", None), - ) - files.append(file_data) - - if recursive and file_data.is_folder: - folder_files = self.list_github_files_in_repo( - credentials, repo_folder=file_data.id, recursive=True - ) - files.extend(folder_files) - - logger.info(f"GitHub repository files retrieved successfully: {len(files)}") - return files diff --git a/backend/api/quivr_api/modules/sync/utils/syncutils.py b/backend/api/quivr_api/modules/sync/utils/syncutils.py deleted file mode 100644 index 9a0735495..000000000 --- a/backend/api/quivr_api/modules/sync/utils/syncutils.py +++ /dev/null @@ -1,417 +0,0 @@ -import io -import os -from datetime import datetime, timezone -from typing import Any, List, Tuple -from uuid import UUID, uuid4 - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.modules.brain.repository.brains_vectors import BrainsVectors -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.dto.inputs import ( - CreateNotification, - NotificationUpdatableProperties, -) -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.dto.inputs import SyncsActiveUpdateInput -from quivr_api.modules.sync.entity.sync_models import ( - DBSyncFile, - DownloadedSyncFile, - SyncFile, - SyncsActive, - SyncsUser, -) -from quivr_api.modules.sync.repository.sync_interfaces import SyncFileInterface -from quivr_api.modules.sync.service.sync_service import ( - ISyncService, - ISyncUserService, -) -from quivr_api.modules.sync.utils.normalize import sanitize_filename -from quivr_api.modules.sync.utils.sync import BaseSync -from quivr_api.modules.upload.service.upload_file import ( - check_file_exists, - upload_file_storage, -) - -logger = get_logger(__name__) - -celery_inspector = celery.control.inspect() - - -# NOTE: we are filtering based on file path names in sync ! -def filter_on_supported_files( - files: list[SyncFile], existing_files: dict[str, DBSyncFile] -) -> list[Tuple[SyncFile, DBSyncFile | None]]: - res = [] - for new_file in files: - prev_file = existing_files.get(new_file.name, None) - if (prev_file and prev_file.supported) or prev_file is None: - res.append((new_file, prev_file)) - - return res - - -def should_download_file( - file: SyncFile, - last_updated_sync_active: datetime | None, - provider_name: str, - datetime_format: str, -) -> bool: - file_last_modified_utc = datetime.strptime( - file.last_modified, datetime_format - ).replace(tzinfo=timezone.utc) - - should_download = ( - last_updated_sync_active is None - or file_last_modified_utc > last_updated_sync_active - ) - - # TODO: Handle notion database - if provider_name == "notion": - should_download &= file.mime_type != "db" - else: - should_download &= not file.is_folder - - return should_download - - -class SyncUtils: - def __init__( - self, - sync_user_service: ISyncUserService, - sync_active_service: ISyncService, - knowledge_service: KnowledgeService, - sync_files_repo: SyncFileInterface, - sync_cloud: BaseSync, - notification_service: NotificationService, - brain_vectors: BrainsVectors, - ) -> None: - self.sync_user_service = sync_user_service - self.sync_active_service = sync_active_service - self.knowledge_service = knowledge_service - self.sync_files_repo = sync_files_repo - self.sync_cloud = sync_cloud - self.notification_service = notification_service - self.brain_vectors = brain_vectors - - # TODO: This modifies the file, we should treat it as such - def create_sync_bulk_notification( - self, files: list[SyncFile], current_user: UUID, brain_id: UUID, bulk_id: UUID - ) -> list[SyncFile]: - res = [] - # TODO: bulk insert in batch - for file in files: - upload_notification = self.notification_service.add_notification( - CreateNotification( - user_id=current_user, - bulk_id=bulk_id, - status=NotificationsStatusEnum.INFO, - title=file.name, - category="sync", - brain_id=str(brain_id), - ) - ) - file.notification_id = upload_notification.id - res.append(file) - return res - - async def download_file( - self, file: SyncFile, credentials: dict[str, Any] - ) -> DownloadedSyncFile: - logger.info(f"Downloading {file} using {self.sync_cloud}") - file_response = await self.sync_cloud.adownload_file(credentials, file) - logger.debug(f"Fetch sync file response: {file_response}") - file_name = str(file_response["file_name"]) - raw_data = file_response["content"] - file_data = ( - io.BufferedReader(raw_data) # type: ignore - if isinstance(raw_data, io.BytesIO) - else io.BufferedReader(raw_data.encode("utf-8")) # type: ignore - ) - extension = os.path.splitext(file_name)[-1].lower() - dfile = DownloadedSyncFile( - file_name=file_name, - file_data=file_data, - extension=extension, - ) - logger.debug(f"Successfully downloaded sync file : {dfile}") - return dfile - - # TODO: REDO THIS MESS !!!! - # REMOVE ALL SYNC TABLES and start from scratch - - async def process_sync_file( - self, - file: SyncFile, - previous_file: DBSyncFile | None, - current_user: SyncsUser, - sync_active: SyncsActive, - ): - logger.info("Processing file: %s", file.name) - brain_id = sync_active.brain_id - source, source_link = self.sync_cloud.name, file.web_view_link - downloaded_file = await self.download_file(file, current_user.credentials) - storage_path = f"{brain_id}/{downloaded_file.file_name}" - exists_in_storage = check_file_exists(str(brain_id), file.name) - - if downloaded_file.extension not in [ - ".pdf", - ".txt", - ".md", - ".csv", - ".docx", - ".xlsx", - ".pptx", - ".doc", - ]: - raise ValueError(f"Incompatible file extension for {downloaded_file}") - - storage_path = sanitize_filename(storage_path) - - response = await upload_file_storage( - downloaded_file.file_data, - storage_path, - upsert=exists_in_storage, - ) - assert response, f"Error uploading {downloaded_file} to {storage_path}" - self.notification_service.update_notification_by_id( - file.notification_id, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.SUCCESS, - description="File downloaded successfully", - ), - ) - # TODO : why knowledge + syncfile, drop syncfile ... - # FIXME : Simplify this logic in KMS plzzz - sync_file_db = self.sync_files_repo.update_or_create_sync_file( - file=file, - previous_file=previous_file, - sync_active=sync_active, - supported=True, - ) - knowledge = await self.knowledge_service.update_or_create_knowledge_sync( - brain_id=brain_id, - file=file, - new_sync_file=sync_file_db, - prev_sync_file=previous_file, - downloaded_file=downloaded_file, - source=source, - source_link=source_link, - user_id=current_user.user_id, - ) - - # Send file for processing - celery.send_task( - "process_file_task", - kwargs={ - "brain_id": brain_id, - "knowledge_id": knowledge.id, - "file_name": storage_path, - "file_original_name": file.name, - "source": source, - "source_link": source_link, - "notification_id": file.notification_id, - }, - ) - return file - - async def process_sync_files( - self, - files: List[SyncFile], - current_user: SyncsUser, - sync_active: SyncsActive, - ): - logger.info(f"Processing {len(files)} for sync_active: {sync_active.id}") - current_user.credentials = self.sync_cloud.check_and_refresh_access_token( - current_user.credentials - ) - - bulk_id = uuid4() - downloaded_files = [] - list_existing_files = self.sync_files_repo.get_sync_files(sync_active.id) - existing_files = {f.path: f for f in list_existing_files} - - supported_files = filter_on_supported_files(files, existing_files) - - files = self.create_sync_bulk_notification( - files, current_user.user_id, sync_active.brain_id, bulk_id - ) - - for file, prev_file in supported_files: - try: - result = await self.process_sync_file( - file=file, - previous_file=prev_file, - current_user=current_user, - sync_active=sync_active, - ) - if result is not None: - downloaded_files.append(result) - - self.notification_service.update_notification_by_id( - file.notification_id, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.SUCCESS, - description="File downloaded successfully", - ), - ) - - except Exception as e: - logger.error( - "An error occurred while syncing %s files: %s", - self.sync_cloud.name, - e, - ) - # TODO: this process_sync_file could fail for a LOT of reason redo this logic - # File isn't supported so we set it as so ? - self.sync_files_repo.update_or_create_sync_file( - file=file, - sync_active=sync_active, - previous_file=prev_file, - supported=False, - ) - self.notification_service.update_notification_by_id( - file.notification_id, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.ERROR, - description="Error downloading file", - ), - ) - - return {"downloaded_files": downloaded_files} - - async def get_files_to_download( - self, sync_active: SyncsActive, user_sync: SyncsUser - ) -> list[SyncFile]: - # Get the folder id from the settings from sync_active - folders = sync_active.settings.get("folders", []) - files_ids = sync_active.settings.get("files", []) - - files = await self.get_syncfiles_from_ids( - user_sync.credentials, - files_ids=files_ids, - folder_ids=folders, - sync_user_id=user_sync.id, - ) - - logger.debug(f"original files to download for {sync_active.id} : {files}") - - last_synced_time = ( - datetime.fromisoformat(sync_active.last_synced).astimezone(timezone.utc) - if sync_active.last_synced - else None - ) - - files_ids = [ - file - for file in files - if should_download_file( - file=file, - last_updated_sync_active=last_synced_time, - provider_name=self.sync_cloud.lower_name, - datetime_format=self.sync_cloud.datetime_format, - ) - ] - - logger.debug(f"filter files to download for {sync_active} : {files_ids}") - return files_ids - - async def get_syncfiles_from_ids( - self, - credentials: dict[str, Any], - files_ids: list[str], - folder_ids: list[str], - sync_user_id: int, - ) -> list[SyncFile]: - files = [] - if self.sync_cloud.lower_name == "notion": - files_ids += folder_ids - - for folder_id in folder_ids: - logger.debug( - f"Recursively getting file_ids from {self.sync_cloud.name}. folder_id={folder_id}" - ) - files.extend( - await self.sync_cloud.aget_files( - credentials=credentials, - sync_user_id=sync_user_id, - folder_id=folder_id, - recursive=True, - ) - ) - if len(files_ids) > 0: - files.extend( - await self.sync_cloud.aget_files_by_id( - credentials=credentials, - file_ids=files_ids, - ) - ) - return files - - async def direct_sync( - self, - sync_active: SyncsActive, - user_sync: SyncsUser, - files_ids: list[str], - folder_ids: list[str], - ): - files = await self.get_syncfiles_from_ids( - user_sync.credentials, files_ids, folder_ids, user_sync.id - ) - processed_files = await self.process_sync_files( - files=files, - current_user=user_sync, - sync_active=sync_active, - ) - - # Update the last_synced timestamp - self.sync_active_service.update_sync_active( - sync_active.id, - SyncsActiveUpdateInput( - last_synced=datetime.now().astimezone().isoformat(), force_sync=False - ), - ) - logger.info( - f"{self.sync_cloud.lower_name} sync completed for sync_active: {sync_active.id}. Synced all {len(processed_files)} files.", - ) - return processed_files - - async def sync( - self, - sync_active: SyncsActive, - user_sync: SyncsUser, - ): - """ - Check if the Specific sync has not been synced and download the folders and files based on the settings. - - Args: - sync_active_id (int): The ID of the active sync. - user_id (str): The user ID associated with the active sync. - """ - logger.info( - "Starting %s sync for sync_active: %s", - self.sync_cloud.lower_name, - sync_active, - ) - - files_to_download = await self.get_files_to_download(sync_active, user_sync) - processed_files = await self.process_sync_files( - files=files_to_download, - current_user=user_sync, - sync_active=sync_active, - ) - - # Update the last_synced timestamp - self.sync_active_service.update_sync_active( - sync_active.id, - SyncsActiveUpdateInput( - last_synced=datetime.now().astimezone().isoformat(), force_sync=False - ), - ) - logger.info( - f"{self.sync_cloud.lower_name} sync completed for sync_active: {sync_active.id}. Synced all {len(processed_files)} files.", - ) - return processed_files diff --git a/backend/api/quivr_api/modules/tools/__init__.py b/backend/api/quivr_api/modules/tools/__init__.py deleted file mode 100644 index adb3f9601..000000000 --- a/backend/api/quivr_api/modules/tools/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .email_sender import EmailSenderTool -from .image_generator import ImageGeneratorTool -from .url_reader import URLReaderTool -from .web_search import WebSearchTool diff --git a/backend/api/quivr_api/modules/tools/email_sender.py b/backend/api/quivr_api/modules/tools/email_sender.py deleted file mode 100644 index 49a38ab63..000000000 --- a/backend/api/quivr_api/modules/tools/email_sender.py +++ /dev/null @@ -1,80 +0,0 @@ -# Extract and combine content recursively -from typing import Dict, Optional, Type - -from langchain.callbacks.manager import ( - AsyncCallbackManagerForToolRun, - CallbackManagerForToolRun, -) -from langchain.pydantic_v1 import BaseModel as BaseModelV1 -from langchain.pydantic_v1 import Field as FieldV1 -from langchain_community.document_loaders import PlaywrightURLLoader -from langchain_core.tools import BaseTool -from pydantic import BaseModel - -from quivr_api.logger import get_logger -from quivr_api.models.settings import BrainSettings, SendEmailSettings -from quivr_api.utils.send_email import send_email - -logger = get_logger(__name__) - - -class EmailInput(BaseModelV1): - text: str = FieldV1( - ..., - title="text", - description="text to send in HTML email format. Use pretty formating, use bold, italic, next line, etc...", - ) - - -class EmailSenderTool(BaseTool): - user_email: str - name = "email-sender" - description = "useful for when you need to send an email." - args_schema: Type[BaseModel] = EmailInput - brain_settings: BrainSettings = BrainSettings() - contact_settings: SendEmailSettings = SendEmailSettings() - - def _run( - self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> Dict: - html_body = """ -
- Quivr Logo -
-
- """ - html_body += f""" - {text} - """ - logger.debug(f"Email body: {html_body}") - logger.debug(f"Email to: {self.user_email}") - logger.debug(f"Email from: {self.contact_settings.resend_contact_sales_from}") - try: - r = send_email( - { - "from": self.contact_settings.resend_contact_sales_from, - "to": self.user_email, - "reply_to": "no-reply@quivr.app", - "subject": "Email from your assistant", - "html": html_body, - } - ) - logger.info("Resend response", r) - except Exception as e: - logger.error(f"Error sending email: {e}") - return {"content": "Error sending email because of error: " + str(e)} - - return {"content": "Email sent"} - - async def _arun( - self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None - ) -> Dict: - """Run the tool asynchronously.""" - loader = PlaywrightURLLoader(urls=[url], remove_selectors=["header", "footer"]) - data = loader.load() - - extracted_content = "" - for page in data: - extracted_content += page.page_content - - return {"content": extracted_content} diff --git a/backend/api/quivr_api/modules/tools/image_generator.py b/backend/api/quivr_api/modules/tools/image_generator.py deleted file mode 100644 index e3b009c07..000000000 --- a/backend/api/quivr_api/modules/tools/image_generator.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Optional, Type - -from langchain.callbacks.manager import ( - AsyncCallbackManagerForToolRun, - CallbackManagerForToolRun, -) -from langchain.pydantic_v1 import BaseModel as BaseModelV1 -from langchain.pydantic_v1 import Field as FieldV1 -from langchain.tools import BaseTool -from langchain_core.tools import BaseTool -from openai import OpenAI -from pydantic import BaseModel - - -class ImageGenerationInput(BaseModelV1): - query: str = FieldV1( - ..., - title="description", - description="A detailled prompt to generate the image from. Takes into account the history of the chat.", - ) - - -class ImageGeneratorTool(BaseTool): - name = "image-generator" - description = "useful for when you need to generate an image from a prompt." - args_schema: Type[BaseModel] = ImageGenerationInput - return_direct = True - - def _run( - self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> str: - client = OpenAI() - - response = client.images.generate( - model="dall-e-3", - prompt=query, - size="1024x1024", - quality="standard", - n=1, - ) - image_url = response.data[0].url - revised_prompt = response.data[0].revised_prompt - # Make the url a markdown image - return f"{revised_prompt} \n ![Generated Image]({image_url}) " - - async def _arun( - self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None - ) -> str: - """Use the tool asynchronously.""" - client = OpenAI() - response = await run_manager.run_async( - client.images.generate, - model="dall-e-3", - prompt=query, - size="1024x1024", - quality="standard", - n=1, - ) - image_url = response.data[0].url - revised_prompt = response.data[0].revised_prompt - # Make the url a markdown image - return f"{revised_prompt} \n ![Generated Image]({image_url}) " diff --git a/backend/api/quivr_api/modules/tools/url_reader.py b/backend/api/quivr_api/modules/tools/url_reader.py deleted file mode 100644 index e1b1086f8..000000000 --- a/backend/api/quivr_api/modules/tools/url_reader.py +++ /dev/null @@ -1,53 +0,0 @@ -# Extract and combine content recursively -import os -from typing import Dict, Optional, Type - -from langchain.callbacks.manager import ( - AsyncCallbackManagerForToolRun, - CallbackManagerForToolRun, -) -from langchain.pydantic_v1 import BaseModel as BaseModelV1 -from langchain.pydantic_v1 import Field as FieldV1 -from langchain_community.document_loaders import PlaywrightURLLoader -from langchain_core.tools import BaseTool -from pydantic import BaseModel - -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - - -class URLReaderInput(BaseModelV1): - url: str = FieldV1(..., title="url", description="url to read") - - -class URLReaderTool(BaseTool): - name = "url-reader" - description = "useful for when you need to read the content of a url." - args_schema: Type[BaseModel] = URLReaderInput - api_key = os.getenv("BRAVE_SEARCH_API_KEY") - - def _run( - self, url: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> Dict: - loader = PlaywrightURLLoader(urls=[url], remove_selectors=["header", "footer"]) - data = loader.load() - - extracted_content = "" - for page in data: - extracted_content += page.page_content - - return {"content": extracted_content} - - async def _arun( - self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None - ) -> Dict: - """Run the tool asynchronously.""" - loader = PlaywrightURLLoader(urls=[url], remove_selectors=["header", "footer"]) - data = loader.load() - - extracted_content = "" - for page in data: - extracted_content += page.page_content - - return {"content": extracted_content} diff --git a/backend/api/quivr_api/modules/tools/web_search.py b/backend/api/quivr_api/modules/tools/web_search.py deleted file mode 100644 index 2c2d004bd..000000000 --- a/backend/api/quivr_api/modules/tools/web_search.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -from typing import Dict, Optional, Type - -import requests -from langchain.callbacks.manager import ( - AsyncCallbackManagerForToolRun, - CallbackManagerForToolRun, -) -from langchain.pydantic_v1 import BaseModel as BaseModelV1 -from langchain.pydantic_v1 import Field as FieldV1 -from langchain_core.tools import BaseTool -from pydantic import BaseModel - -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - - -class WebSearchInput(BaseModelV1): - query: str = FieldV1(..., title="query", description="search query to look up") - - -class WebSearchTool(BaseTool): - name = "brave-web-search" - description = "useful for when you need to search the web for something." - args_schema: Type[BaseModel] = WebSearchInput - api_key: str = os.getenv("BRAVE_SEARCH_API_KEY", "") - - def _check_environment_variable(self) -> bool: - """Check if the environment variable is set.""" - - return os.getenv("BRAVE_SEARCH_API_KEY") is not None - - def __init__(self): - if not self._check_environment_variable(): - raise ValueError("BRAVE_SEARCH_API_KEY environment variable is not set") - super().__init__() - - def _run( - self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None - ) -> Dict: - """Run the tool.""" - headers = { - "Accept": "application/json", - "Accept-Encoding": "gzip", - "X-Subscription-Token": self.api_key, - } - response = requests.get( - f"https://api.search.brave.com/res/v1/web/search?q={query}&count=3", - headers=headers, - ) - return self._parse_response(response.json()) - - async def _arun( - self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None - ) -> Dict: - """Run the tool asynchronously.""" - headers = { - "Accept": "application/json", - "Accept-Encoding": "gzip", - "X-Subscription-Token": self.api_key, - } - response = requests.get( - f"https://api.search.brave.com/res/v1/web/search?q={query}&count=3", - headers=headers, - ) - return self._parse_response(response.json()) - - def _parse_response(self, response: Dict) -> str: - """Parse the response.""" - short_results = [] - results = response["web"]["results"] - for result in results: - title = result["title"] - url = result["url"] - description = result["description"] - short_results.append(self._format_result(title, description, url)) - return "\n".join(short_results) - - def _format_result(self, title: str, description: str, url: str) -> str: - return f"**{title}**\n{description}\n{url}" - - -if __name__ == "__main__": - tool = WebSearchTool() - print(tool.run("python")) diff --git a/backend/api/quivr_api/modules/upload/__init__.py b/backend/api/quivr_api/modules/upload/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/upload/controller/__init__.py b/backend/api/quivr_api/modules/upload/controller/__init__.py deleted file mode 100644 index 56030aa68..000000000 --- a/backend/api/quivr_api/modules/upload/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .upload_routes import upload_router diff --git a/backend/api/quivr_api/modules/upload/controller/upload_routes.py b/backend/api/quivr_api/modules/upload/controller/upload_routes.py deleted file mode 100644 index f7e285da5..000000000 --- a/backend/api/quivr_api/modules/upload/controller/upload_routes.py +++ /dev/null @@ -1,141 +0,0 @@ -import io -import os -from typing import Annotated, Optional -from uuid import UUID - -from fastapi import ( - APIRouter, - BackgroundTasks, - Depends, - HTTPException, - Query, - UploadFile, -) - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.brain.entity.brain_entity import RoleEnum -from quivr_api.modules.brain.service.brain_authorization_service import ( - validate_brain_authorization, -) -from quivr_api.modules.dependencies import get_service, get_supabase_async_client -from quivr_api.modules.knowledge.dto.inputs import CreateKnowledgeProperties -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.dto.inputs import ( - CreateNotification, - NotificationUpdatableProperties, -) -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.utils.normalize import sanitize_filename -from quivr_api.modules.upload.service.upload_file import ( - upload_file_storage, -) -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_usage import UserUsage -from quivr_api.utils.byte_size import convert_bytes -from quivr_api.utils.telemetry import maybe_send_telemetry -from supabase.client import AsyncClient - -logger = get_logger(__name__) -upload_router = APIRouter() - -notification_service = NotificationService() -KnowledgeServiceDep = Annotated[ - KnowledgeService, Depends(get_service(KnowledgeService)) -] - -AsyncClientDep = Annotated[AsyncClient, Depends(get_supabase_async_client)] - - -@upload_router.post("/upload", dependencies=[Depends(AuthBearer())], tags=["Upload"]) -async def upload_file( - uploadFile: UploadFile, - knowledge_service: KnowledgeServiceDep, - background_tasks: BackgroundTasks, - bulk_id: Optional[UUID] = Query(None, description="The ID of the bulk upload"), - brain_id: UUID = Query(..., description="The ID of the brain"), - current_user: UserIdentity = Depends(get_current_user), - integration: Optional[str] = None, - integration_link: Optional[str] = None, -): - validate_brain_authorization( - brain_id, current_user.id, [RoleEnum.Editor, RoleEnum.Owner] - ) - user_daily_usage = UserUsage( - id=current_user.id, - email=current_user.email, - ) - user_settings = user_daily_usage.get_user_settings() - remaining_free_space = user_settings.get("max_brain_size", 1 << 30) # 1GB - if remaining_free_space - uploadFile.size < 0: - message = f"Brain will exceed maximum capacity. Maximum file allowed is : {convert_bytes(remaining_free_space)}" - raise HTTPException(status_code=403, detail=message) - - file_name = sanitize_filename(str(uploadFile.filename)) - - # TODO: Later - upload_notification = notification_service.add_notification( - CreateNotification( - user_id=current_user.id, - bulk_id=bulk_id, - status=NotificationsStatusEnum.INFO, - title=file_name, - category="upload", - brain_id=str(brain_id), - ) - ) - - background_tasks.add_task( - maybe_send_telemetry, "upload_file", {"file_name": file_name} - ) - - filename_with_brain_id = str(brain_id) + "/" + file_name - - buff_reader = io.BufferedReader(uploadFile.file) # type: ignore - try: - await upload_file_storage(buff_reader, filename_with_brain_id) - except Exception as e: - logger.exception(f"Exception in upload_route {e}") - notification_service.update_notification_by_id( - upload_notification.id if upload_notification else None, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.ERROR, - description="There was an error uploading the file", - ), - ) - raise HTTPException( - status_code=500, detail=f"Failed to upload file to storage. {e}" - ) - - knowledge_to_add = CreateKnowledgeProperties( - brain_id=brain_id, - file_name=file_name, - extension=os.path.splitext( - file_name # pyright: ignore reportPrivateUsage=none - )[-1].lower(), - source=integration if integration else "local", - source_link=integration_link, # FIXME: Should return the s3 link @chloedia - file_size=uploadFile.size, - file_sha1=None, - ) - knowledge = await knowledge_service.insert_knowledge_brain( - user_id=current_user.id, knowledge_to_add=knowledge_to_add - ) # type: ignore - - celery.send_task( - "process_file_task", - kwargs={ - "file_name": filename_with_brain_id, - "file_original_name": file_name, - "brain_id": brain_id, - "notification_id": upload_notification.id, - "knowledge_id": knowledge.id, - "source": integration, - "source_link": integration_link, # supabase_client.storage.from_("quivr").get_public_url(uploadFile.filename) - }, - ) - return {"message": "File processing has started."} diff --git a/backend/api/quivr_api/modules/upload/service/__init__.py b/backend/api/quivr_api/modules/upload/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/upload/service/generate_file_signed_url.py b/backend/api/quivr_api/modules/upload/service/generate_file_signed_url.py deleted file mode 100644 index 089e509b9..000000000 --- a/backend/api/quivr_api/modules/upload/service/generate_file_signed_url.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from multiprocessing import get_logger - -from quivr_api.modules.dependencies import get_supabase_client -from supabase.client import Client - -logger = get_logger() - -SIGNED_URL_EXPIRATION_PERIOD_IN_SECONDS = 3600 -EXTERNAL_SUPABASE_URL = os.getenv("EXTERNAL_SUPABASE_URL", None) -SUPABASE_URL = os.getenv("SUPABASE_URL", None) - - -def generate_file_signed_url(path): - supabase_client: Client = get_supabase_client() - - try: - response = supabase_client.storage.from_("quivr").create_signed_url( - path, - SIGNED_URL_EXPIRATION_PERIOD_IN_SECONDS, - options={ - "download": True, - "transform": None, - }, - ) - logger.info("RESPONSE SIGNED URL", response) - # Replace in the response the supabase url by the external supabase url in the object signedURL - if EXTERNAL_SUPABASE_URL and SUPABASE_URL: - response["signedURL"] = response["signedURL"].replace( - SUPABASE_URL, EXTERNAL_SUPABASE_URL - ) - return response - except Exception as e: - logger.error(e) diff --git a/backend/api/quivr_api/modules/upload/service/list_files.py b/backend/api/quivr_api/modules/upload/service/list_files.py deleted file mode 100644 index b6a4abd8f..000000000 --- a/backend/api/quivr_api/modules/upload/service/list_files.py +++ /dev/null @@ -1,16 +0,0 @@ -from multiprocessing import get_logger - -from quivr_api.modules.dependencies import get_supabase_client -from supabase.client import Client - -logger = get_logger() - - -def list_files_from_storage(path): - supabase_client: Client = get_supabase_client() - - try: - response = supabase_client.storage.from_("quivr").list(path) - return response - except Exception as e: - logger.error(e) diff --git a/backend/api/quivr_api/modules/upload/service/upload_file.py b/backend/api/quivr_api/modules/upload/service/upload_file.py deleted file mode 100644 index d787d1139..000000000 --- a/backend/api/quivr_api/modules/upload/service/upload_file.py +++ /dev/null @@ -1,75 +0,0 @@ -import mimetypes -from io import BufferedReader, FileIO - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import ( - get_supabase_async_client, - get_supabase_client, -) -from supabase import Client - -logger = get_logger(__name__) - - -def check_file_exists(brain_id: str, file_identifier: str) -> bool: - supabase_client: Client = get_supabase_client() - try: - # Check if the file exists - logger.info(f"Checking if file {file_identifier} exists.") - # This needs to be converted into a file_identifier that is safe for a URL - response = supabase_client.storage.from_("quivr").list(brain_id) - # Check if the file_identifier is in the response - file_exists = any( - file["name"].split(".")[0] == file_identifier.split(".")[0] - for file in response - ) - if file_exists: - logger.info(f"File {file_identifier} exists.") - return True - else: - logger.info(f"File {file_identifier} does not exist.") - return False - except Exception as e: - logger.error(f"An error occurred while checking the file: {e}") - return True - - -async def upload_file_storage( - file: FileIO | BufferedReader | bytes, - storage_path: str, - upsert: bool = False, -): - supabase_client = await get_supabase_async_client() - mime_type, _ = mimetypes.guess_type(storage_path) - logger.debug( - f"Uploading file to {storage_path} using supabase. upsert={upsert}, mimetype={mime_type}" - ) - - if upsert: - response = await supabase_client.storage.from_("quivr").update( - storage_path, - file, # type: ignore - file_options={ - "content-type": mime_type or "application/html", - "upsert": "true", - "cache-control": "3600", - }, - ) - return response - else: - # check if file sha1 is already in storage - try: - response = await supabase_client.storage.from_("quivr").upload( - storage_path, - file, # type: ignore - file_options={ - "content-type": mime_type or "application/html", - "upsert": "false", - "cache-control": "3600", - }, - ) - return response - except Exception as e: - if "The resource already exists" in str(e) and not upsert: - raise FileExistsError(f"File {storage_path} already exists") - raise e diff --git a/backend/api/quivr_api/modules/upload/tests/test_files/test.bib b/backend/api/quivr_api/modules/upload/tests/test_files/test.bib deleted file mode 100644 index 14b61838b..000000000 --- a/backend/api/quivr_api/modules/upload/tests/test_files/test.bib +++ /dev/null @@ -1,6 +0,0 @@ -@Article{citekey, - author = "", - title = "", - journal = "", - year = "", -} diff --git a/backend/api/quivr_api/modules/upload/tests/test_files/test.csv b/backend/api/quivr_api/modules/upload/tests/test_files/test.csv deleted file mode 100644 index a025eb598..000000000 --- a/backend/api/quivr_api/modules/upload/tests/test_files/test.csv +++ /dev/null @@ -1,17 +0,0 @@ -quivrhq/quivr,Sat May 13 2023 02:20:09 GMT+0200 (heure d’été d’Europe centrale),0 -quivrhq/quivr,Tue May 16 2023 18:03:49 GMT+0200 (heure d’été d’Europe centrale),660 -quivrhq/quivr,Thu May 18 2023 03:04:23 GMT+0200 (heure d’été d’Europe centrale),1380 -quivrhq/quivr,Thu May 18 2023 23:04:11 GMT+0200 (heure d’été d’Europe centrale),2070 -quivrhq/quivr,Sat May 20 2023 04:44:40 GMT+0200 (heure d’été d’Europe centrale),2790 -quivrhq/quivr,Sun May 21 2023 03:19:46 GMT+0200 (heure d’été d’Europe centrale),3510 -quivrhq/quivr,Mon May 22 2023 08:03:18 GMT+0200 (heure d’été d’Europe centrale),4230 -quivrhq/quivr,Tue May 23 2023 16:57:58 GMT+0200 (heure d’été d’Europe centrale),4950 -quivrhq/quivr,Sat May 27 2023 02:18:31 GMT+0200 (heure d’été d’Europe centrale),5640 -quivrhq/quivr,Thu Jun 01 2023 18:45:27 GMT+0200 (heure d’été d’Europe centrale),6360 -quivrhq/quivr,Thu Jun 08 2023 16:33:57 GMT+0200 (heure d’été d’Europe centrale),7080 -quivrhq/quivr,Mon Jun 19 2023 12:58:34 GMT+0200 (heure d’été d’Europe centrale),7800 -quivrhq/quivr,Tue Jun 27 2023 14:45:52 GMT+0200 (heure d’été d’Europe centrale),8520 -quivrhq/quivr,Fri Jun 30 2023 11:43:51 GMT+0200 (heure d’été d’Europe centrale),9210 -quivrhq/quivr,Fri Jul 07 2023 23:08:23 GMT+0200 (heure d’été d’Europe centrale),9930 -quivrhq/quivr,Mon Jul 10 2023 08:13:07 GMT+0200 (heure d’été d’Europe centrale),10650 -quivrhq/quivr,Wed Jul 12 2023 09:40:29 GMT+0200 (heure d’été d’Europe centrale),13837 diff --git a/backend/api/quivr_api/modules/upload/tests/test_files/test.pdf b/backend/api/quivr_api/modules/upload/tests/test_files/test.pdf deleted file mode 100644 index e256f71d7..000000000 Binary files a/backend/api/quivr_api/modules/upload/tests/test_files/test.pdf and /dev/null differ diff --git a/backend/api/quivr_api/modules/upload/tests/test_files/test.txt b/backend/api/quivr_api/modules/upload/tests/test_files/test.txt deleted file mode 100644 index a8a940627..000000000 --- a/backend/api/quivr_api/modules/upload/tests/test_files/test.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test \ No newline at end of file diff --git a/backend/api/quivr_api/modules/upload/tests/test_upload.py b/backend/api/quivr_api/modules/upload/tests/test_upload.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/user/__init__.py b/backend/api/quivr_api/modules/user/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/user/controller/__init__.py b/backend/api/quivr_api/modules/user/controller/__init__.py deleted file mode 100644 index d02e1c740..000000000 --- a/backend/api/quivr_api/modules/user/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .user_controller import user_router diff --git a/backend/api/quivr_api/modules/user/controller/user_controller.py b/backend/api/quivr_api/modules/user/controller/user_controller.py deleted file mode 100644 index e10c9e7aa..000000000 --- a/backend/api/quivr_api/modules/user/controller/user_controller.py +++ /dev/null @@ -1,126 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Depends, Request - -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.models.service.model_service import ModelService -from quivr_api.modules.user.dto.inputs import UserUpdatableProperties -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.repository.users import Users -from quivr_api.modules.user.service.user_usage import UserUsage - -user_router = APIRouter() -brain_user_service = BrainUserService() -ModelServiceDep = Annotated[ModelService, Depends(get_service(ModelService))] -user_repository = Users() - - -@user_router.get("/user", dependencies=[Depends(AuthBearer())], tags=["User"]) -async def get_user_endpoint( - request: Request, - model_service: ModelServiceDep, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Get user information and statistics. - - - `current_user`: The current authenticated user. - - Returns the user's email, maximum brain size, current brain size, maximum requests number, requests statistics, and the current date. - - This endpoint retrieves information and statistics about the authenticated user. It includes the user's email, maximum brain size, - current brain size, maximum requests number, requests statistics, and the current date. The brain size is calculated based on the - user's uploaded vectors, and the maximum brain size is obtained from the environment variables. The requests statistics provide - information about the user's API usage. - """ - - user_daily_usage = UserUsage( - id=current_user.id, - email=current_user.email, - ) - user_settings = user_daily_usage.get_user_settings() - max_brain_size = user_settings.get("max_brain_size", 1000000000) - max_brains = user_settings.get("max_brains") - - monthly_chat_credit = user_settings.get("monthly_chat_credit", 10) - - user_daily_usage = UserUsage(id=current_user.id) - models = await model_service.get_models() - models_names = [model.name for model in models] - return { - "email": current_user.email, - "max_brain_size": max_brain_size, - "max_brains": max_brains, - "current_brain_size": 0, - "monthly_chat_credit": monthly_chat_credit, - "models": models_names, - "id": current_user.id, - "is_premium": user_settings["is_premium"], - } - - -@user_router.put( - "/user/identity", - dependencies=[Depends(AuthBearer())], - tags=["User"], -) -def update_user_identity_route( - user_identity_updatable_properties: UserUpdatableProperties, - current_user: UserIdentity = Depends(get_current_user), -) -> UserIdentity: - """ - Update user identity. - """ - return user_repository.update_user_properties( - current_user.id, user_identity_updatable_properties - ) - - -@user_router.get( - "/user/identity", - dependencies=[Depends(AuthBearer())], - tags=["User"], -) -def get_user_identity_route( - current_user: UserIdentity = Depends(get_current_user), -) -> UserIdentity: - """ - Get user identity. - """ - return user_repository.get_user_identity(current_user.id) - - -@user_router.delete( - "/user_data", - dependencies=[Depends(AuthBearer())], - tags=["User"], -) -async def delete_user_data_route( - current_user: UserIdentity = Depends(get_current_user), -): - """ - Delete a user. - - - `user_id`: The ID of the user to delete. - - This endpoint deletes a user from the system. - """ - - user_repository.delete_user_data(current_user.id) - - return {"message": "User deleted successfully"} - - -@user_router.get( - "/user/credits", - dependencies=[Depends(AuthBearer())], - tags=["User"], -) -def get_user_credits( - current_user: UserIdentity = Depends(get_current_user), -) -> int: - """ - Get user remaining credits. - """ - return user_repository.get_user_credits(current_user.id) diff --git a/backend/api/quivr_api/modules/user/dto/inputs.py b/backend/api/quivr_api/modules/user/dto/inputs.py deleted file mode 100644 index 348e99af6..000000000 --- a/backend/api/quivr_api/modules/user/dto/inputs.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class UserUpdatableProperties(BaseModel): - # Nothing for now - username: Optional[str] = None - company: Optional[str] = None - onboarded: Optional[bool] = None - company_size: Optional[str] = None - usage_purpose: Optional[str] = None diff --git a/backend/api/quivr_api/modules/user/entity/user_identity.py b/backend/api/quivr_api/modules/user/entity/user_identity.py deleted file mode 100644 index 3f734f1a6..000000000 --- a/backend/api/quivr_api/modules/user/entity/user_identity.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import List, Optional -from uuid import UUID, uuid4 - -from pydantic import BaseModel -from sqlmodel import Field, Relationship, SQLModel - - -class User(SQLModel, table=True): - __tablename__ = "users" # type: ignore - - id: UUID | None = Field( - primary_key=True, - nullable=False, - default_factory=uuid4, - ) - email: str - onboarded: bool | None = None - chats: List["Chat"] | None = Relationship(back_populates="user") # type: ignore - notion_syncs: List["NotionSyncFile"] | None = Relationship(back_populates="user") # type: ignore - - -class UserIdentity(BaseModel): - id: UUID - email: Optional[str] = None - username: Optional[str] = None - company: Optional[str] = None - onboarded: Optional[bool] = None - company_size: Optional[str] = None - usage_purpose: Optional[str] = None diff --git a/backend/api/quivr_api/modules/user/repository/__init__.py b/backend/api/quivr_api/modules/user/repository/__init__.py deleted file mode 100644 index dba60627d..000000000 --- a/backend/api/quivr_api/modules/user/repository/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .users import Users diff --git a/backend/api/quivr_api/modules/user/repository/users.py b/backend/api/quivr_api/modules/user/repository/users.py deleted file mode 100644 index 18d25a228..000000000 --- a/backend/api/quivr_api/modules/user/repository/users.py +++ /dev/null @@ -1,136 +0,0 @@ -import time - -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.repository.users_interface import UsersInterface -from quivr_api.modules.user.service import user_usage - - -class Users(UsersInterface): - def __init__(self): - supabase_client = get_supabase_client() - self.db = supabase_client - - def create_user_identity(self, id): - response = ( - self.db.from_("user_identity") - .insert( - { - "user_id": str(id), - } - ) - .execute() - ) - user_identity = response.data[0] - return UserIdentity(id=user_identity.get("user_id")) - - def update_user_properties( - self, - user_id, - user_identity_updatable_properties, - ): - response = ( - self.db.from_("user_identity") - .update(user_identity_updatable_properties.__dict__) - .filter("user_id", "eq", user_id) # type: ignore - .execute() - ) - - if len(response.data) == 0: - return self.create_user_identity(user_id) - - user_identity = response.data[0] - - return UserIdentity(id=user_id) - - def get_user_identity(self, user_id): - response = ( - self.db.from_("user_identity") - .select("*, users (email)") - .filter("user_id", "eq", str(user_id)) - .execute() - ) - - if len(response.data) == 0: - return self.create_user_identity(user_id) - - user_identity = response.data[0] - - user_identity["id"] = user_id # Add 'id' field to the dictionary - user_identity["email"] = user_identity["users"]["email"] - return UserIdentity(**user_identity) - - def get_user_id_by_user_email(self, email): - response = ( - self.db.rpc("get_user_id_by_user_email", {"user_email": email}) - .execute() - .data - ) - if len(response) > 0: - return response[0]["user_id"] - return None - - def get_user_email_by_user_id(self, user_id): - response = self.db.rpc( - "get_user_email_by_user_id", {"user_id": str(user_id)} - ).execute() - return response.data[0]["email"] - - def delete_user_data(self, user_id): - response = ( - self.db.from_("brains_users") - .select("brain_id") - .filter("rights", "eq", "Owner") - .filter("user_id", "eq", str(user_id)) - .execute() - ) - brain_ids = [row["brain_id"] for row in response.data] - - for brain_id in brain_ids: - self.db.table("brains").delete().filter( - "brain_id", "eq", brain_id - ).execute() - - for brain_id in brain_ids: - self.db.table("brains_vectors").delete().filter( - "brain_id", "eq", brain_id - ).execute() - - for brain_id in brain_ids: - self.db.table("chat_history").delete().filter( - "brain_id", "eq", brain_id - ).execute() - - self.db.table("user_settings").delete().filter( - "user_id", "eq", str(user_id) - ).execute() - self.db.table("user_identity").delete().filter( - "user_id", "eq", str(user_id) - ).execute() - self.db.table("users").delete().filter("id", "eq", str(user_id)).execute() - - def get_user_credits(self, user_id): - try: - user_usage_instance = user_usage.UserUsage(id=user_id) - - user_monthly_usage = user_usage_instance.get_user_monthly_usage( - time.strftime("%Y%m%d") - ) - - response = self.db.from_("user_settings").select("monthly_chat_credit").filter( - "user_id", "eq", str(user_id) - ).execute() - - if not response.data: - raise ValueError("No data found for user settings") - - monthly_chat_credit = response.data[0].get("monthly_chat_credit") - if monthly_chat_credit is None: - raise ValueError("Monthly chat credit not found") - - return monthly_chat_credit - user_monthly_usage - - except Exception as e: - # Log the exception or handle it as needed - print(f"An error occurred while getting user credits: {e}") - return 25 # or a default value, depending on your needs diff --git a/backend/api/quivr_api/modules/user/repository/users_interface.py b/backend/api/quivr_api/modules/user/repository/users_interface.py deleted file mode 100644 index f284018f2..000000000 --- a/backend/api/quivr_api/modules/user/repository/users_interface.py +++ /dev/null @@ -1,63 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import UUID - -from quivr_api.modules.user.dto.inputs import UserUpdatableProperties -from quivr_api.modules.user.entity.user_identity import UserIdentity - - -class UsersInterface(ABC): - @abstractmethod - def create_user_identity(self, id: UUID) -> UserIdentity: - """ - Create a user identity - """ - pass - - @abstractmethod - def update_user_properties( - self, - user_id: UUID, - user_identity_updatable_properties: UserUpdatableProperties, - ) -> UserIdentity: - """ - Update the user properties - """ - pass - - @abstractmethod - def get_user_identity(self, user_id: UUID) -> UserIdentity: - """ - Get the user identity - """ - pass - - @abstractmethod - def get_user_id_by_user_email(self, email: str) -> UUID | None: - """ - Get the user id by user email - """ - pass - - @abstractmethod - def get_user_email_by_user_id(self, user_id: UUID) -> str: - """ - Get the user email by user id - """ - pass - - @abstractmethod - def delete_user_data(self, user_id: str): - """ - Delete a user. - - - `user_id`: The ID of the user to delete. - - This endpoint deletes a user from the system. - """ - - @abstractmethod - def get_user_credits(self, user_id: UUID) -> int: - """ - Get user remaining credits - """ - pass diff --git a/backend/api/quivr_api/modules/user/service/__init__.py b/backend/api/quivr_api/modules/user/service/__init__.py deleted file mode 100644 index 254962a48..000000000 --- a/backend/api/quivr_api/modules/user/service/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .user_service import UserService diff --git a/backend/api/quivr_api/modules/user/service/user_service.py b/backend/api/quivr_api/modules/user/service/user_service.py deleted file mode 100644 index 1873520fc..000000000 --- a/backend/api/quivr_api/modules/user/service/user_service.py +++ /dev/null @@ -1,17 +0,0 @@ -from uuid import UUID - -from quivr_api.modules.user.repository.users import Users -from quivr_api.modules.user.repository.users_interface import UsersInterface - - -class UserService: - repository: UsersInterface - - def __init__(self): - self.repository = Users() - - def get_user_id_by_email(self, email: str) -> UUID | None: - return self.repository.get_user_id_by_user_email(email) - - def get_user_email_by_user_id(self, user_id: UUID) -> str | None: - return self.repository.get_user_email_by_user_id(user_id) diff --git a/backend/api/quivr_api/modules/user/service/user_usage.py b/backend/api/quivr_api/modules/user/service/user_usage.py deleted file mode 100644 index ccb5817aa..000000000 --- a/backend/api/quivr_api/modules/user/service/user_usage.py +++ /dev/null @@ -1,96 +0,0 @@ -from quivr_api.logger import get_logger -from quivr_api.models.databases.supabase.supabase import SupabaseDB -from quivr_api.models.settings import PostHogSettings -from quivr_api.modules.dependencies import get_supabase_db -from quivr_api.modules.user.entity.user_identity import UserIdentity - -logger = get_logger(__name__) - - -class UserUsage(UserIdentity): - daily_requests_count: int = 0 - - def __init__(self, **data): - super().__init__(**data) - - @property - def supabase_db(self) -> SupabaseDB: - return get_supabase_db() - - def get_user_daily_usage(self): - """ - Fetch the user request stats from the database - """ - request = self.supabase_db.get_user_usage(self.id) - return request - - def get_models(self): - """ - Fetch the user request stats from the database - """ - request = self.supabase_db.get_models() - - return request - - def get_user_settings(self): - """ - Fetch the user settings from the database - """ - posthog = PostHogSettings() - request = self.supabase_db.get_user_settings(self.id) - if request is not None and request.get("is_premium", False): - posthog.set_once_user_properties( - self.id, "HAS_OR_HAD_PREMIUM", {"is_was_premium": "true"} - ) - posthog.set_user_properties( - self.id, "CURRENT_PREMIUM", {"is_premium": "true"} - ) - else: - posthog.set_user_properties( - self.id, "CURRENT_PREMIUM", {"is_premium": "false"} - ) - - return request - - def get_user_monthly_usage(self, date): - """ - Fetch the user monthly usage from the database - """ - request = self.supabase_db.get_user_requests_count_for_month(self.id, date) - - return request - - def handle_increment_user_request_count(self, date, number=1): - """ - Increment the user request count in the database - """ - current_requests_count = self.supabase_db.get_user_requests_count_for_month( - self.id, date - ) - - daily_requests_count = self.supabase_db.get_user_requests_count_for_day( - self.id, date - ) - - # BUG: could be a bug, we are assuming that 0 means no records ! - if daily_requests_count == 0: - logger.info("Request count is 0, creating new record") - if self.email is None: - raise ValueError("User Email should be defined for daily usage table") - self.supabase_db.create_user_daily_usage( - user_id=self.id, date=date, user_email=self.email, number=number - ) - self.daily_requests_count = number - return - - self.supabase_db.increment_user_request_count( - user_id=self.id, - date=date, - number=daily_requests_count + number, - ) - - self.daily_requests_count = current_requests_count + number - - logger.info( - f"User {self.email} request count updated to {self.daily_requests_count}" - ) diff --git a/backend/api/quivr_api/modules/user/tests/test_user_controller.py b/backend/api/quivr_api/modules/user/tests/test_user_controller.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/vector/__init__.py b/backend/api/quivr_api/modules/vector/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/vector/entity/vector.py b/backend/api/quivr_api/modules/vector/entity/vector.py deleted file mode 100644 index b0583f640..000000000 --- a/backend/api/quivr_api/modules/vector/entity/vector.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Optional -from uuid import UUID - -from pgvector.sqlalchemy import Vector as PGVector -from pydantic import BaseModel -from quivr_api.models.settings import settings -from sqlalchemy import Column -from sqlmodel import JSON, Column, Field, SQLModel, text -from sqlmodel import UUID as PGUUID - - -class Vector(SQLModel, table=True): - __tablename__ = "vectors" # type: ignore - id: UUID | None = Field( - default=None, - sa_column=Column( - PGUUID, - server_default=text("uuid_generate_v4()"), - primary_key=True, - ), - ) - content: str = Field(default=None) - metadata_: dict = Field(default={}, sa_column=Column("metadata", JSON, default={})) - embedding: Optional[PGVector] = Field( - sa_column=Column(PGVector(settings.embedding_dim)), - ) # Verify with text_ada -> put it in Env variabme - knowledge_id: UUID = Field(default=None, foreign_key="knowledge.id") - - class Config: - arbitrary_types_allowed = True - - -class VectorType(BaseModel): - id: UUID | None - content: str - metadata_: dict - knowledge_id: UUID - - -class SimilaritySearchOutput(BaseModel): - id: UUID - brain_id: UUID - knowledge_id: UUID - content: str - metadata_: dict - embedding: str - similarity: float diff --git a/backend/api/quivr_api/modules/vector/repository/vectors_repository.py b/backend/api/quivr_api/modules/vector/repository/vectors_repository.py deleted file mode 100644 index a7a5a4b63..000000000 --- a/backend/api/quivr_api/modules/vector/repository/vectors_repository.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import Any, List, Sequence -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseRepository -from quivr_api.modules.vector.entity.vector import SimilaritySearchOutput, Vector -from sqlalchemy import exc, text -from sqlmodel import Session, select - -logger = get_logger(__name__) - - -class VectorRepository(BaseRepository): - def __init__(self, session: Session): - super().__init__(session) - self.session = session - - def create_vectors(self, new_vectors: List[Vector]) -> List[Vector]: - try: - # Use SQLAlchemy session to add and commit the new vector - self.session.add_all(new_vectors) - self.session.commit() - except exc.IntegrityError: - # Rollback the session if there’s an IntegrityError - self.session.rollback() - raise Exception("Integrity error occurred while creating vector.") - except Exception as e: - self.session.rollback() - print(f"Error: {e}") - raise Exception(f"An error occurred while creating vector: {e}") - - # Refresh the session to get any updated fields (like auto-generated IDs) - for vector in new_vectors: - self.session.refresh(vector) - - return new_vectors - - def get_vectors_by_knowledge_id(self, knowledge_id: UUID) -> Sequence[Vector]: - query = select(Vector).where(Vector.knowledge_id == knowledge_id) - results = self.session.execute(query) - return results.scalars().all() - - def similarity_search( - self, - query_embedding: List[float], - brain_id: UUID, - k: int = 40, - max_chunk_sum: int = 10000, # Example value - **kwargs: Any, - ) -> Sequence[SimilaritySearchOutput]: - sql_query = text(""" - WITH ranked_vectors AS ( - SELECT - v.id AS vector_id, - kb.brain_id AS vector_brain_id, - v.knowledge_id AS vector_knowledge_id, - v.content AS vector_content, - v.metadata AS vector_metadata, - v.embedding AS vector_embedding, - 1 - (v.embedding <=> (:query_embedding)::vector) AS calculated_similarity, - (v.metadata->>'chunk_size')::integer AS chunk_size - FROM - vectors v - INNER JOIN - knowledge_brain kb ON v.knowledge_id = kb.knowledge_id - WHERE - kb.brain_id = :p_brain_id - ORDER BY - calculated_similarity DESC - ), filtered_vectors AS ( - SELECT - vector_id, - vector_brain_id, - vector_knowledge_id, - vector_content, - vector_metadata, - vector_embedding, - calculated_similarity, - chunk_size, - sum(chunk_size) OVER (ORDER BY calculated_similarity DESC) AS running_total - FROM ranked_vectors - ) - SELECT - vector_id AS id, - vector_brain_id AS brain_id, - vector_knowledge_id AS knowledge_id, - vector_content AS content, - vector_metadata AS metadata, - vector_embedding AS embedding, - calculated_similarity AS similarity - FROM filtered_vectors - WHERE running_total <= :max_chunk_sum - LIMIT :k - """) - - params = { - "query_embedding": query_embedding, - "p_brain_id": brain_id, - "k": k, - "max_chunk_sum": max_chunk_sum, - } - - result = self.session.execute(sql_query, params=params) - full_results = result.all() - formated_result = [ - SimilaritySearchOutput( - id=row.id, - brain_id=row.brain_id, - knowledge_id=row.knowledge_id, - content=row.content, - metadata_=row.metadata, - embedding=row.embedding, - similarity=row.similarity, - ) - for row in full_results - ] - return formated_result diff --git a/backend/api/quivr_api/modules/vector/service/vector_service.py b/backend/api/quivr_api/modules/vector/service/vector_service.py deleted file mode 100644 index 6a775dd6e..000000000 --- a/backend/api/quivr_api/modules/vector/service/vector_service.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import List -from uuid import UUID - -from langchain.docstore.document import Document -from langchain.embeddings.base import Embeddings -from quivr_api.logger import get_logger -from quivr_api.modules.dependencies import BaseService, get_embedding_client -from quivr_api.modules.vector.entity.vector import Vector -from quivr_api.modules.vector.repository.vectors_repository import VectorRepository - -logger = get_logger(__name__) - - -class VectorService(BaseService[VectorRepository]): - repository_cls = VectorRepository - _embedding: Embeddings = get_embedding_client() - - def __init__(self, repository: VectorRepository): - self.repository = repository - - def create_vectors(self, chunks: List[Document], knowledge_id: UUID) -> List[UUID]: - # Vector is created upon the user's first question asked - logger.info( - f"New vector entry in vectors table for knowledge_id {knowledge_id}" - ) - # FIXME ADD a check in case of failure - embeddings = self._embedding.embed_documents( - [chunk.page_content for chunk in chunks] - ) - new_vectors = [ - Vector( - content=chunk.page_content, - metadata_=chunk.metadata, - embedding=embeddings[i], # type: ignore - knowledge_id=knowledge_id, - ) - for i, chunk in enumerate(chunks) - ] - created_vector = self.repository.create_vectors(new_vectors) - - return [vector.id for vector in created_vector if vector.id] - - def similarity_search(self, query: str, brain_id: UUID, k: int = 40): - vectors = self._embedding.embed_documents([query]) - query_embedding = vectors[0] - vectors = self.repository.similarity_search( - query_embedding=query_embedding, brain_id=brain_id, k=k - ) - - match_result = [ - Document( - metadata={ - **search.metadata_, - "id": search.id, - "similarity": search.similarity, - }, - page_content=search.content, - ) - for search in vectors - if search.content - ] - - return match_result diff --git a/backend/api/quivr_api/modules/vector/tests/__init__.py b/backend/api/quivr_api/modules/vector/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/modules/vector/tests/test_vectors.py b/backend/api/quivr_api/modules/vector/tests/test_vectors.py deleted file mode 100644 index ce4b6f04b..000000000 --- a/backend/api/quivr_api/modules/vector/tests/test_vectors.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import List, Tuple - -import pytest -from langchain.docstore.document import Document -from langchain_core.embeddings import DeterministicFakeEmbedding -from sqlmodel import Session, select - -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB -from quivr_api.modules.user.entity.user_identity import User -from quivr_api.modules.vector.entity.vector import Vector -from quivr_api.modules.vector.repository.vectors_repository import VectorRepository -from quivr_api.modules.vector.service.vector_service import VectorService - -pg_database_base_url = "postgresql://postgres:postgres@localhost:54322/postgres" - -TestData = Tuple[List[Vector], KnowledgeDB, Brain] - - -@pytest.fixture(scope="module") -def embedder(): - return DeterministicFakeEmbedding(size=1536) - - -@pytest.fixture(scope="function") -def test_data(sync_session: Session, embedder) -> TestData: - user_1 = ( - sync_session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - assert user_1.id - vectors = embedder.embed_documents( - [ - "vector_1", - "vector_2", - ] - ) - - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - knowledge_1 = KnowledgeDB( - file_name="test_file_1", - extension=".txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[brain_1], - user_id=user_1.id, - ) - sync_session.add(knowledge_1) - sync_session.commit() - sync_session.refresh(knowledge_1) - - assert knowledge_1.id, "Knowledge ID not generated" - - vector_1 = Vector( - content="vector_1", - metadata_={"chunk_size": 96}, - embedding=vectors[0], # type: ignore - knowledge_id=knowledge_1.id, - ) - - vector_2 = Vector( - content="vector_2", - metadata_={"chunk_size": 96}, - embedding=vectors[1], # type: ignore - knowledge_id=knowledge_1.id, - ) - - sync_session.add(vector_1) - sync_session.add(vector_2) - - sync_session.commit() - - return ([vector_1, vector_2], knowledge_1, brain_1) - - -def test_create_vectors_service(sync_session: Session, test_data: TestData, embedder): - _, knowledge, _ = test_data - assert knowledge.id - repo = VectorRepository(sync_session) - service = VectorService(repo) - service._embedding = embedder - - chunk_1 = Document(page_content="I love eating pasta with tomato sauce") - chunk_2 = Document(page_content="I love eating pizza with extra cheese") - - # Create vectors from documents - new_vectors_id: List[int] = service.create_vectors([chunk_1, chunk_2], knowledge.id) # type: ignore - - # Verify the correct number of vectors were created - assert len(new_vectors_id) == 2, f"Expected 2 vectors, got {len(new_vectors_id)}" - - # Verify the content of the first vector matches the corresponding document - vector_1_content = ( - sync_session.execute(select(Vector).where(Vector.id == new_vectors_id[0])) - .scalars() - .first() - .content - ) - vector_2_content = ( - sync_session.execute(select(Vector).where(Vector.id == new_vectors_id[1])) - .scalars() - .first() - .content - ) - - assert ( - vector_1_content == chunk_1.page_content - ), "The content of the first vector does not match" - assert ( - vector_2_content == chunk_2.page_content - ), "The content of the second vector does not match" - - -def test_get_vectors_by_knowledge_id(sync_session: Session, test_data: TestData): - vectors, knowledge, _ = test_data - assert knowledge.id - - repo = VectorRepository(sync_session) - results = repo.get_vectors_by_knowledge_id(knowledge.id) # type: ignore - - assert len(results) == 2, f"Expected 2 vectors, got {len(results)}" - assert ( - results[0].content == vectors[0].content - ), f"Expected {vectors[0].content}, got {results[0].content}" - assert ( - results[1].content == vectors[1].content - ), f"Expected {vectors[1].content}, got {results[1].content}" - - -def test_service_similarity_search( - sync_session: Session, test_data: TestData, embedder -): - vectors, knowledge, brain = test_data - assert knowledge.id - assert brain.brain_id - - repo = VectorRepository(sync_session) - service = VectorService(repo) - service._embedding = embedder - - k = 2 - results = service.similarity_search(vectors[0].content, brain.brain_id, k=k) # type: ignore - assert len(results) == k - assert results[0].page_content == vectors[0].content - - results = service.similarity_search(vectors[1].content, brain.brain_id, k=k) # type: ignore - - assert results[0].page_content == vectors[1].content - - k = 1 - results = service.similarity_search(vectors[0].content, brain.brain_id, k=k) # type: ignore - assert len(results) == k - assert results[0].page_content == vectors[0].content - - results = service.similarity_search(vectors[1].content, brain.brain_id, k=k) # type: ignore - - assert results[0].page_content == vectors[1].content - - -def test_similarity_search(sync_session: Session, test_data: TestData): - vectors, knowledge, brain = test_data - assert knowledge.id - assert brain.brain_id - - repo = VectorRepository(sync_session) - - k = 2 - results = repo.similarity_search(vectors[0].embedding, brain.brain_id, k=k) # type: ignore - assert len(results) == k - assert results[0].content == vectors[0].content - - results = repo.similarity_search(vectors[1].embedding, brain.brain_id, k=k) # type: ignore - - assert results[0].content == vectors[1].content - - k = 1 - results = repo.similarity_search(vectors[0].embedding, brain.brain_id, k=k) # type: ignore - assert len(results) == k - assert results[0].content == vectors[0].content - - results = repo.similarity_search(vectors[1].embedding, brain.brain_id, k=k) # type: ignore - - assert results[0].content == vectors[1].content - - -def test_similarity_with_oversized_chunk(sync_session: Session, test_data: TestData): - vectors, knowledge, brain = test_data - assert knowledge.id - assert brain.brain_id - - repo = VectorRepository(sync_session) - - k = 2 - results = repo.similarity_search( - vectors[0].embedding, # type: ignore - brain.brain_id, - k=k, - max_chunk_sum=100, - ) - - assert len(results) == 1 - assert results[0].content == vectors[0].content diff --git a/backend/api/quivr_api/routes/__init__.py b/backend/api/quivr_api/routes/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/routes/crawl_routes.py b/backend/api/quivr_api/routes/crawl_routes.py deleted file mode 100644 index 6af4fc55c..000000000 --- a/backend/api/quivr_api/routes/crawl_routes.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import Annotated, Optional -from uuid import UUID - -from fastapi import APIRouter, Depends, Query - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth import AuthBearer, get_current_user -from quivr_api.models.crawler import CrawlWebsite -from quivr_api.modules.brain.entity.brain_entity import RoleEnum -from quivr_api.modules.brain.service.brain_authorization_service import ( - validate_brain_authorization, -) -from quivr_api.modules.dependencies import get_service -from quivr_api.modules.knowledge.dto.inputs import CreateKnowledgeProperties -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.dto.inputs import CreateNotification -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_usage import UserUsage -from quivr_api.utils.byte_size import convert_bytes - -logger = get_logger(__name__) -crawl_router = APIRouter() - -notification_service = NotificationService() -KnowledgeServiceDep = Annotated[ - KnowledgeService, Depends(get_service(KnowledgeService)) -] - - -@crawl_router.get("/crawl/healthz", tags=["Health"]) -async def healthz(): - return {"status": "ok"} - - -@crawl_router.post("/crawl", dependencies=[Depends(AuthBearer())], tags=["Crawl"]) -async def crawl_endpoint( - crawl_website: CrawlWebsite, - knowledge_service: KnowledgeServiceDep, - bulk_id: Optional[UUID] = Query(None, description="The ID of the bulk upload"), - brain_id: UUID = Query(..., description="The ID of the brain"), - chat_id: Optional[UUID] = Query(None, description="The ID of the chat"), - current_user: UserIdentity = Depends(get_current_user), -): - """ - Crawl a website and process the crawled data. - """ - - validate_brain_authorization( - brain_id, current_user.id, [RoleEnum.Editor, RoleEnum.Owner] - ) - - userDailyUsage = UserUsage( - id=current_user.id, - email=current_user.email, - ) - userSettings = userDailyUsage.get_user_settings() - - file_size = 1000000 - remaining_free_space = userSettings.get("max_brain_size", 1000000000) - - if remaining_free_space - file_size < 0: - message = { - "message": f"⌠UserIdentity's brain will exceed maximum capacity with this upload. Maximum file allowed is : {convert_bytes(remaining_free_space)}", - "type": "error", - } - else: - upload_notification = notification_service.add_notification( - CreateNotification( - user_id=current_user.id, - bulk_id=bulk_id, - status=NotificationsStatusEnum.INFO, - title=f"{crawl_website.url}", - category="crawl", - brain_id=str(brain_id), - ) - ) - knowledge_to_add = CreateKnowledgeProperties( - brain_id=brain_id, - file_name=crawl_website.url, - url=crawl_website.url, - extension=".html", - source="web", - source_link=crawl_website.url, - ) - - added_knowledge = await knowledge_service.insert_knowledge_brain( - knowledge_to_add=knowledge_to_add, user_id=current_user.id - ) - logger.info(f"Knowledge {added_knowledge} added successfully") - - celery.send_task( - "process_crawl_task", - kwargs={ - "crawl_website_url": crawl_website.url, - "brain_id": brain_id, - "knowledge_id": added_knowledge.id, - "notification_id": upload_notification.id, - }, - ) - - return {"message": "Crawl processing has started."} - return message diff --git a/backend/api/quivr_api/routes/headers/__init_.py b/backend/api/quivr_api/routes/headers/__init_.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/routes/headers/get_origin_header.py b/backend/api/quivr_api/routes/headers/get_origin_header.py deleted file mode 100644 index fe8e3c1ac..000000000 --- a/backend/api/quivr_api/routes/headers/get_origin_header.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Optional - -from fastapi import Header - - -def get_origin_header(origin: Optional[str] = Header(None)): - return origin diff --git a/backend/api/quivr_api/routes/subscription_routes.py b/backend/api/quivr_api/routes/subscription_routes.py deleted file mode 100644 index cecf227c3..000000000 --- a/backend/api/quivr_api/routes/subscription_routes.py +++ /dev/null @@ -1,458 +0,0 @@ -from typing import List -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException -from pydantic import BaseModel - -from quivr_api.logger import get_logger -from quivr_api.middlewares.auth.auth_bearer import AuthBearer, get_current_user -from quivr_api.models.brains_subscription_invitations import BrainSubscription -from quivr_api.modules.brain.entity.brain_entity import RoleEnum -from quivr_api.modules.brain.repository import IntegrationBrain -from quivr_api.modules.brain.service.brain_authorization_service import ( - has_brain_authorization, - validate_brain_authorization, -) -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.brain.service.brain_subscription import ( - SubscriptionInvitationService, - resend_invitation_email, -) -from quivr_api.modules.brain.service.brain_user_service import BrainUserService -from quivr_api.modules.prompt.entity.prompt import PromptStatusEnum -from quivr_api.modules.prompt.service.prompt_service import PromptService -from quivr_api.modules.user.entity.user_identity import UserIdentity -from quivr_api.modules.user.service.user_service import UserService -from quivr_api.routes.headers.get_origin_header import get_origin_header - -logger = get_logger(__name__) - -subscription_router = APIRouter() -subscription_service = SubscriptionInvitationService() -user_service = UserService() -prompt_service = PromptService() -brain_user_service = BrainUserService() -brain_service = BrainService() -integration_brains_repository = IntegrationBrain() - - -@subscription_router.post( - "/brains/{brain_id}/subscription", - dependencies=[ - Depends( - AuthBearer(), - ), - Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])), - Depends(get_origin_header), - ], - tags=["BrainSubscription"], -) -def invite_users_to_brain( - brain_id: UUID, - users: List[dict], - origin: str = Depends(get_origin_header), - current_user: UserIdentity = Depends(get_current_user), -): - """ - Invite multiple users to a brain by their emails. This function creates - or updates a brain subscription invitation for each user and sends an - invitation email to each user. - """ - for user in users: - subscription = BrainSubscription( - brain_id=brain_id, email=user["email"], rights=user["rights"] - ) - # check if user is an editor but trying to give high level permissions - if subscription.rights == "Owner": - try: - validate_brain_authorization( - brain_id, - current_user.id, - RoleEnum.Owner, - ) - except HTTPException: - raise HTTPException( - status_code=403, - detail="You don't have the rights to give owner permissions", - ) - - try: - should_send_invitation_email = ( - subscription_service.create_or_update_subscription_invitation( - subscription - ) - ) - if should_send_invitation_email: - resend_invitation_email( - subscription, - inviter_email=current_user.email or "Quivr", - user_id=current_user.id, - origin=origin, - ) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error inviting user: {e}") - - return {"message": "Invitations sent successfully"} - - -@subscription_router.get( - "/brains/{brain_id}/users", - dependencies=[ - Depends(AuthBearer()), - Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])), - ], -) -def get_users_with_brain_access( - brain_id: UUID, -): - """ - Get all users for a brain - """ - - brain_users = brain_user_service.get_brain_users( - brain_id=brain_id, - ) - - brain_access_list = [] - - for brain_user in brain_users: - brain_access = {} - # TODO: find a way to fetch user email concurrently - brain_access["email"] = user_service.get_user_email_by_user_id( - brain_user.user_id - ) - brain_access["rights"] = brain_user.rights - brain_access_list.append(brain_access) - - return brain_access_list - - -@subscription_router.delete( - "/brains/{brain_id}/subscription", -) -async def remove_user_subscription( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) -): - """ - Remove a user's subscription to a brain - """ - targeted_brain = brain_service.get_brain_by_id(brain_id) - - if targeted_brain is None: - raise HTTPException( - status_code=404, - detail="Brain not found while trying to delete", - ) - - user_brain = brain_user_service.get_brain_for_user(current_user.id, brain_id) - if user_brain is None: - raise HTTPException( - status_code=403, - detail="You don't have permission for this brain", - ) - - if user_brain.rights != "Owner": - brain_user_service.delete_brain_user(current_user.id, brain_id) - else: - brain_users = brain_user_service.get_brain_users( - brain_id=brain_id, - ) - brain_other_owners = [ - brain - for brain in brain_users - if brain.rights == "Owner" and str(brain.user_id) != str(current_user.id) - ] - - if len(brain_other_owners) == 0: - brain_service.delete_brain( - brain_id=brain_id, - ) - if targeted_brain.prompt_id: - brain_to_delete_prompt = prompt_service.get_prompt_by_id( - targeted_brain.prompt_id - ) - if brain_to_delete_prompt is not None and ( - brain_to_delete_prompt.status == PromptStatusEnum.private - ): - prompt_service.delete_prompt_by_id(targeted_brain.prompt_id) - - else: - brain_user_service.delete_brain_user( - current_user.id, - brain_id, - ) - - return {"message": f"Subscription removed successfully from brain {brain_id}"} - - -@subscription_router.get( - "/brains/{brain_id}/subscription", - dependencies=[Depends(AuthBearer())], - tags=["BrainSubscription"], -) -def get_user_invitation( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) -): - """ - Get an invitation to a brain for a user. This function checks if the user - has been invited to the brain and returns the invitation status. - """ - if not current_user.email: - raise HTTPException(status_code=400, detail="UserIdentity email is not defined") - - subscription = BrainSubscription(brain_id=brain_id, email=current_user.email) - - invitation = subscription_service.fetch_invitation(subscription) - - if invitation is None: - raise HTTPException( - status_code=404, - detail="You have not been invited to this brain", - ) - - brain_details = brain_service.get_brain_details(brain_id, current_user.id) - - if brain_details is None: - raise HTTPException( - status_code=404, - detail="Brain not found while trying to get invitation", - ) - - return {"name": brain_details.name, "rights": invitation["rights"]} - - -@subscription_router.post( - "/brains/{brain_id}/subscription/accept", - tags=["Brain"], -) -async def accept_invitation( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) -): - """ - Accept an invitation to a brain for a user. This function removes the - invitation from the subscription invitations and adds the user to the - brain users. - """ - if not current_user.email: - raise HTTPException(status_code=400, detail="UserIdentity email is not defined") - - subscription = BrainSubscription(brain_id=brain_id, email=current_user.email) - - try: - invitation = subscription_service.fetch_invitation(subscription) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error fetching invitation: {e}") - - if not invitation: - raise HTTPException(status_code=404, detail="Invitation not found") - - try: - brain_user_service.create_brain_user( - user_id=current_user.id, - brain_id=brain_id, - rights=invitation["rights"], - is_default_brain=False, - ) - shared_brain = brain_service.get_brain_by_id(brain_id) - - except Exception as e: - logger.error(f"Error adding user to brain: {e}") - raise HTTPException(status_code=400, detail=f"Error adding user to brain: {e}") - - try: - subscription_service.remove_invitation(subscription) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error removing invitation: {e}") - - return {"message": "Invitation accepted successfully"} - - -@subscription_router.post( - "/brains/{brain_id}/subscription/decline", - tags=["Brain"], -) -async def decline_invitation( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) -): - """ - Decline an invitation to a brain for a user. This function removes the - invitation from the subscription invitations. - """ - if not current_user.email: - raise HTTPException(status_code=400, detail="UserIdentity email is not defined") - - subscription = BrainSubscription(brain_id=brain_id, email=current_user.email) - - try: - invitation = subscription_service.fetch_invitation(subscription) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error fetching invitation: {e}") - - if not invitation: - raise HTTPException(status_code=404, detail="Invitation not found") - - try: - subscription_service.remove_invitation(subscription) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error removing invitation: {e}") - - return {"message": "Invitation declined successfully"} - - -class BrainSubscriptionUpdatableProperties(BaseModel): - rights: str | None = None - email: str - - -@subscription_router.put( - "/brains/{brain_id}/subscription", - dependencies=[ - Depends(AuthBearer()), - Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])), - ], -) -def update_brain_subscription( - brain_id: UUID, - subscription: BrainSubscriptionUpdatableProperties, - current_user: UserIdentity = Depends(get_current_user), -): - user_email = subscription.email - if user_email == current_user.email: - raise HTTPException( - status_code=403, - detail="You can't change your own permissions", - ) - - user_id = user_service.get_user_id_by_email(user_email) - - if user_id is None: - raise HTTPException( - status_code=404, - detail="User not found", - ) - - # check if user is an editor but trying to give high level permissions - if subscription.rights == "Owner": - try: - validate_brain_authorization( - brain_id, - current_user.id, - RoleEnum.Owner, - ) - except HTTPException: - raise HTTPException( - status_code=403, - detail="You don't have the rights to give owner permissions", - ) - - # check if user is not an editor trying to update an owner right which is not allowed - current_invitation = brain_user_service.get_brain_for_user(user_id, brain_id) - if current_invitation is not None and current_invitation.rights == "Owner": - try: - validate_brain_authorization( - brain_id, - current_user.id, - RoleEnum.Owner, - ) - except HTTPException: - raise HTTPException( - status_code=403, - detail="You can't change the permissions of an owner", - ) - - # removing user access from brain - if subscription.rights is None: - try: - # only owners can remove user access to a brain - validate_brain_authorization( - brain_id, - current_user.id, - RoleEnum.Owner, - ) - brain_user_service.delete_brain_user(user_id, brain_id) - except HTTPException: - raise HTTPException( - status_code=403, - detail="You don't have the rights to remove user access", - ) - else: - brain_user_service.update_brain_user_rights( - brain_id, user_id, subscription.rights - ) - - return {"message": "Brain subscription updated successfully"} - - -@subscription_router.post( - "/brains/{brain_id}/subscribe", - tags=["Subscription"], -) -async def subscribe_to_brain_handler( - brain_id: UUID, - secrets: dict = {}, - current_user: UserIdentity = Depends(get_current_user), -): - """ - Subscribe to a public brain - """ - if not current_user.email: - raise HTTPException(status_code=400, detail="UserIdentity email is not defined") - - brain = brain_service.get_brain_by_id(brain_id) - - if brain is None: - raise HTTPException(status_code=404, detail="Brain not found") - if brain.status != "public": - raise HTTPException( - status_code=403, - detail="You cannot subscribe to this brain without invitation", - ) - # check if user is already subscribed to brain - user_brain = brain_user_service.get_brain_for_user(current_user.id, brain_id) - if user_brain is not None: - raise HTTPException( - status_code=403, - detail="You are already subscribed to this brain", - ) - - try: - brain_user_service.create_brain_user( - user_id=current_user.id, - brain_id=brain_id, - rights=RoleEnum.Viewer, - is_default_brain=False, - ) - except Exception as e: - raise HTTPException(status_code=400, detail=f"Error adding user to brain: {e}") - - return {"message": "You have successfully subscribed to the brain"} - - -@subscription_router.post( - "/brains/{brain_id}/unsubscribe", - tags=["Subscription"], -) -async def unsubscribe_from_brain_handler( - brain_id: UUID, current_user: UserIdentity = Depends(get_current_user) -): - """ - Unsubscribe from a brain - """ - if not current_user.email: - raise HTTPException(status_code=400, detail="UserIdentity email is not defined") - - brain = brain_service.get_brain_by_id(brain_id) - - if brain is None: - raise HTTPException(status_code=404, detail="Brain not found") - - # check if user is already subscribed to brain - user_brain = brain_user_service.get_brain_for_user(current_user.id, brain_id) - if user_brain is None: - raise HTTPException( - status_code=403, - detail="You are not subscribed to this brain", - ) - brain_user_service.delete_brain_user(user_id=current_user.id, brain_id=brain_id) - - return {"message": "You have successfully unsubscribed from the brain"} diff --git a/backend/api/quivr_api/utils/__init__.py b/backend/api/quivr_api/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/utils/byte_size.py b/backend/api/quivr_api/utils/byte_size.py deleted file mode 100644 index fde05a017..000000000 --- a/backend/api/quivr_api/utils/byte_size.py +++ /dev/null @@ -1,11 +0,0 @@ -def convert_bytes(bytes: int, precision=2): - """Converts bytes into a human-friendly format.""" - abbreviations = ["B", "KB", "MB"] - if bytes <= 0: - return "0 B" - size = bytes - index = 0 - while size >= 1024 and index < len(abbreviations) - 1: - size /= 1024 - index += 1 - return f"{size:.{precision}f} {abbreviations[index]}" diff --git a/backend/api/quivr_api/utils/parse_message_time.py b/backend/api/quivr_api/utils/parse_message_time.py deleted file mode 100644 index 4aa18a3b8..000000000 --- a/backend/api/quivr_api/utils/parse_message_time.py +++ /dev/null @@ -1,5 +0,0 @@ -from datetime import datetime - - -def parse_message_time(message_time_str: str): - return datetime.strptime(message_time_str, "%Y-%m-%dT%H:%M:%S.%f") diff --git a/backend/api/quivr_api/utils/partial.py b/backend/api/quivr_api/utils/partial.py deleted file mode 100644 index 138a36c71..000000000 --- a/backend/api/quivr_api/utils/partial.py +++ /dev/null @@ -1,50 +0,0 @@ -from copy import deepcopy -from typing import Any, Callable, Optional, Type, TypeVar -from uuid import UUID - -from pydantic import BaseModel, create_model -from pydantic.fields import FieldInfo - -Model = TypeVar("Model", bound=Type[BaseModel]) - - -def all_optional(without_fields: list[str] | None = None) -> Callable[[Model], Model]: - if without_fields is None: - without_fields = [] - - def wrapper(model: Type[Model]) -> Type[Model]: - base_model: Type[Model] = model - - def make_field_optional( - field: FieldInfo, default: Any = None - ) -> tuple[Any, FieldInfo]: - new = deepcopy(field) - new.default = default - new.annotation = Optional[field.annotation] - return new.annotation, new - - if without_fields: - base_model = BaseModel - - return create_model( - model.__name__, - __base__=base_model, - __module__=model.__module__, - **{ - field_name: make_field_optional(field_info) - for field_name, field_info in model.model_fields.items() - if field_name not in without_fields - }, - ) - - return wrapper - - -class Test(BaseModel): - id: UUID - name: Optional[str] = None - - -@all_optional() -class TestUpdate(Test): - pass diff --git a/backend/api/quivr_api/utils/send_email.py b/backend/api/quivr_api/utils/send_email.py deleted file mode 100644 index 8a759a683..000000000 --- a/backend/api/quivr_api/utils/send_email.py +++ /dev/null @@ -1,43 +0,0 @@ -import smtplib -from typing import Dict - -import resend - -from quivr_api.models.settings import ResendSettings - - -def send_email(params: Dict): - settings = ResendSettings() - if settings.resend_api_key != "null": - resend.api_key = settings.resend_api_key - return resend.Emails.send(params) - else: - # Use SMTP to send the email - smtp_server = settings.quivr_smtp_server - smtp_port = settings.quivr_smtp_port - smtp_username = settings.quivr_smtp_username - smtp_password = settings.quivr_smtp_password - - try: - with smtplib.SMTP(smtp_server, smtp_port) as server: - if smtp_port == 587: - server.starttls() - if smtp_username and smtp_password: - server.login(smtp_username, smtp_password) - - from_address = params.get("from", "mail@team.quivr.app") - to_addresses = params.get("to", []) - subject = params.get("subject", "") - html_content = params.get("html", "") - - message = f"From: {from_address}\n" - message += f"To: {', '.join(to_addresses)}\n" - message += f"Subject: {subject}\n" - message += "Content-Type: text/html\n\n" - message += html_content - - server.sendmail(from_address, to_addresses, message) - - return {"message": "Email sent successfully"} - except Exception as e: - raise Exception(f"Failed to send email: {str(e)}") diff --git a/backend/api/quivr_api/utils/telemetry.py b/backend/api/quivr_api/utils/telemetry.py deleted file mode 100644 index e6c94f351..000000000 --- a/backend/api/quivr_api/utils/telemetry.py +++ /dev/null @@ -1,61 +0,0 @@ -import hashlib -import json -import os - -import httpx -from fastapi import Request - -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - -# Assume these are your Supabase Function endpoint and any necessary headers -TELEMETRY_URL = "https://ovbvcnwemowuuuaebizd.supabase.co/functions/v1/telemetry" -HEADERS = { - "Content-Type": "application/json", -} - - -def generate_machine_key(): - # Get the OpenAI API key from the environment variables - seed = os.getenv("OPENAI_API_KEY") or "" - - # Use SHA-256 hash to generate a unique key from the seed - unique_key = hashlib.sha256(seed.encode()).hexdigest() - - return unique_key - - -def send_telemetry(event_name: str, event_data: dict, request: Request | None = None): - # Generate a unique machine key - machine_key = generate_machine_key() - domain = None - if request: - domain = request.url.hostname - logger.info(f"Domain: {domain}") - event_data = {**event_data, "domain": domain} - # Prepare the payload - payload = json.dumps( - { - "anonymous_identifier": machine_key, - "event_name": event_name, - "event_data": event_data, - } - ) - - # TODO: client should only live once - # Send the telemetry data - with httpx.Client() as client: - _ = client.post(TELEMETRY_URL, headers=HEADERS, data=payload) - - -def maybe_send_telemetry( - event_name: str, - event_data: dict, - request: Request | None = None, -): - enable_telemetry = os.getenv("TELEMETRY_ENABLED", "false") - if enable_telemetry.lower() == "false": - return - - send_telemetry(event_name, event_data, request) diff --git a/backend/api/quivr_api/utils/uuid_generator.py b/backend/api/quivr_api/utils/uuid_generator.py deleted file mode 100644 index d910bf380..000000000 --- a/backend/api/quivr_api/utils/uuid_generator.py +++ /dev/null @@ -1,11 +0,0 @@ -import hashlib -import uuid - - -def generate_uuid_from_string(input_string): - # Hash the input string using SHA-1 (or any other hash function) - hash_obj = hashlib.sha1(input_string.encode()) - # Get the hexadecimal digest of the hash - hash_hex = hash_obj.hexdigest() - # Create a UUID from the first 32 characters of the hash - return uuid.UUID(hash_hex[:32]) diff --git a/backend/api/quivr_api/vectorstore/__init__.py b/backend/api/quivr_api/vectorstore/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/api/quivr_api/vectorstore/supabase.py b/backend/api/quivr_api/vectorstore/supabase.py deleted file mode 100644 index dfca682f0..000000000 --- a/backend/api/quivr_api/vectorstore/supabase.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Any, List -from uuid import UUID - -from langchain.docstore.document import Document -from langchain.embeddings.base import Embeddings -from langchain_community.vectorstores import SupabaseVectorStore - -from quivr_api.logger import get_logger - -# from quivr_api.modules.dependencies import get_pg_database_engine -from quivr_api.modules.vector.service.vector_service import VectorService -from supabase.client import Client - -logger = get_logger(__name__) -# engine = get_pg_database_engine() -# Session = sessionmaker(bind=engine) - - -class CustomSupabaseVectorStore(SupabaseVectorStore): - """A custom vector store that uses the match_vectors table instead of the vectors table.""" - - def __init__( - self, - client: Client, - embedding: Embeddings, - table_name: str, - vector_service: VectorService, - brain_id: UUID | None = None, - user_id: UUID | None = None, - number_docs: int = 35, - max_input: int = 2000, - ): - super().__init__(client, embedding, table_name) - self.brain_id = brain_id - self.user_id = user_id - self.number_docs = number_docs - self.max_input = max_input - self.vector_service = vector_service - - def find_brain_closest_query( - self, - user_id: str, - query: str, - k: int = 6, - table: str = "match_brain", - threshold: float = 0.5, - ) -> list[dict[str, Any]]: - vectors = self._embedding.embed_documents([query]) - query_embedding = vectors[0] - - res = self._client.rpc( - table, - { - "query_embedding": query_embedding, - "match_count": self.number_docs, - "p_user_id": str(self.user_id), - }, - ).execute() - - # Get the brain_id of the brain that is most similar to the query - # Get the brain_id and name of the brains that are most similar to the query - brain_details = [ - { - "id": item.get("id", None), - "name": item.get("name", None), - "similarity": item.get("similarity", 0.0), - } - for item in res.data - ] - return brain_details - - def similarity_search( - self, - query: str, - k: int = 40, - table: str = "match_vectors", - threshold: float = 0.5, - **kwargs: Any, - ) -> List[Document]: - logger.debug(f"Similarity search for query: {query}") - assert self.brain_id, "Brain ID is required for similarity search" - - match_result = self.vector_service.similarity_search( - query, brain_id=self.brain_id, k=k - ) - - sorted_match_result_by_file_name_metadata = sorted( - match_result, - key=lambda x: ( - x.metadata.get("file_name", ""), - x.metadata.get("index", float("inf")), - ), - ) - - return sorted_match_result_by_file_name_metadata diff --git a/backend/api/tests/settings/test_settings.py b/backend/api/tests/settings/test_settings.py deleted file mode 100644 index 6a89d5b78..000000000 --- a/backend/api/tests/settings/test_settings.py +++ /dev/null @@ -1,46 +0,0 @@ -from unittest.mock import MagicMock, patch - -from langchain_community.embeddings.ollama import OllamaEmbeddings -from langchain_openai import AzureOpenAIEmbeddings -from quivr_api.modules.dependencies import get_embedding_client - - -def test_ollama_embedding(): - with patch("quivr_api.modules.dependencies.settings") as mock_settings: - mock_settings.ollama_api_base_url = "http://ollama.example.com" - mock_settings.azure_openai_embeddings_url = None - - embedding_client = get_embedding_client() - - assert isinstance(embedding_client, OllamaEmbeddings) - assert embedding_client.base_url == "http://ollama.example.com" - - -def test_azure_embedding(): - with patch("quivr_api.modules.dependencies.settings") as mock_settings: - mock_settings.ollama_api_base_url = None - mock_settings.azure_openai_embeddings_url = "https://quivr-test.openai.azure.com/openai/deployments/embedding/embeddings?api-version=2023-05-15" - - embedding_client = get_embedding_client() - - assert isinstance(embedding_client, AzureOpenAIEmbeddings) - assert embedding_client.azure_endpoint == "https://quivr-test.openai.azure.com" - - -def test_openai_embedding(): - with ( - patch("quivr_api.modules.dependencies.settings") as mock_settings, - patch( - "quivr_api.modules.dependencies.OpenAIEmbeddings" - ) as mock_openai_embeddings, - ): - mock_settings.ollama_api_base_url = None - mock_settings.azure_openai_embeddings_url = None - - # Create a mock instance for OpenAIEmbeddings - mock_openai_instance = MagicMock() - mock_openai_embeddings.return_value = mock_openai_instance - - embedding_client = get_embedding_client() - - assert embedding_client == mock_openai_instance diff --git a/backend/ci-migration.sh b/backend/ci-migration.sh deleted file mode 100644 index aa624f8b2..000000000 --- a/backend/ci-migration.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -echo "Setting the project ID from environment variable" -PROJECT_ID=$PROJECT_ID - -echo "Setting the supabase token from environment variables" -SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN - -echo "Setting the supabase db password from environment variable" -SUPABASE_DB_PASSWORD=$SUPABASE_DB_PASSWORD - -echo "Logging in to supabase" -supabase login --token $SUPABASE_ACCESS_TOKEN - -echo "Running supabase link" -supabase link --project-ref $PROJECT_ID --password $SUPABASE_DB_PASSWORD - -echo "Running supabase db push" -supabase db push --linked --password $SUPABASE_DB_PASSWORD - diff --git a/backend/core/README.md b/backend/core/README.md deleted file mode 100644 index c8e3ac4a8..000000000 --- a/backend/core/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# quivr-core package - -The RAG of Quivr.com - -## Contributors - -### Requirements - -0. Install [poetry](https://python-poetry.org/docs/). Recommand the `pipx` install -1. (Optional) Install (`uv`)[https://github.com/astral-sh/uv] -2. git clone `quivr` - -``` -git clone git@github.com:QuivrHQ/quivr.git -cd quivr/backend/core -``` - -2. Create virtual environement with your preferred tool - - ``` - uv venv - ``` - -3. Install `base` quivr-core environment - - ``` - poetry install -E base --with dev,test - ``` - -4. Install pre-commit - - ``` - pre-commit install - ``` - -5. Run example - ``` - python examples/simple_question.py - ``` - -## Backend - -0. Install [poetry](https://python-poetry.org/docs/). Recommand the `pipx` install -1. (Optional) Install (`uv`)[https://github.com/astral-sh/uv] -2. Clone `quivr` - -``` -cd quivr/backend/ -``` - -2. Create virtual environement with your preferred tool - - ``` - uv venv - ``` - -3. Install quivr-core monorepo - - ``` - poetry install - ``` - -4. Copy `.env.example` to `.env` and modify env variables : step 2 : (https://docs.quivr.app/install#60-seconds-installation) - -5. Run backend-api - -``` -LOG_LEVEL=debug uvicorn quivr_api.main:app --log-level debug --reload --host 0.0.0.0 --port 5050 --workers 1 -``` diff --git a/backend/core/examples/chatbot/requirements.txt b/backend/core/examples/chatbot/requirements.txt deleted file mode 100644 index 9f39bc404..000000000 --- a/backend/core/examples/chatbot/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -quivr-core[base]==0.0.8 -chainlit==1.1.306 \ No newline at end of file diff --git a/backend/core/quivr_core/processor/__init__.py b/backend/core/quivr_core/processor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/quivr_core/processor/implementations/__init__.py b/backend/core/quivr_core/processor/implementations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/quivr_core/processor/megaparse/multimodal_convertor/__init__.py b/backend/core/quivr_core/processor/megaparse/multimodal_convertor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/quivr_core/storage/__init__.py b/backend/core/quivr_core/storage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/__init__.py b/backend/core/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/__init__.py b/backend/core/tests/processor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/community/__init__.py b/backend/core/tests/processor/community/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/docx/__init__.py b/backend/core/tests/processor/docx/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/epub/__init__.py b/backend/core/tests/processor/epub/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/odt/__init__.py b/backend/core/tests/processor/odt/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/tests/processor/pdf/__init__.py b/backend/core/tests/processor/pdf/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/pyproject.toml b/backend/pyproject.toml deleted file mode 100644 index 80298fb94..000000000 --- a/backend/pyproject.toml +++ /dev/null @@ -1,109 +0,0 @@ -[project] -name = "quivr-monorepo" -version = "0.0.1" -description = "Quivr Monorepo" -authors = [ - { name = "Stan Girard", email = "stan@quivr.app" }, - { name = "Amine Diro", email = "amine@quivr.app" }, - { name = "Chloé Daems", email = "chloe@quivr.app" }, - { name = "Jacopo Chevallard", email = "jacopo@quivr.app" }, -] -dependencies = [ - "packaging>=22.0", - # Logging packages - "structlog>=24.4.0", - "python-json-logger>=2.0.7", - "orjson>=3.10.7", -] -readme = "README.md" -requires-python = ">= 3.11" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.rye] -managed = true -virtual = false -universal = true -dev-dependencies = [ - "mypy>=1.11.1", - "pre-commit>=3.8.0", - "ipykernel>=6.29.5", - "ruff>=0.6.0", - "flake8>=7.1.1", - "flake8-black>=0.3.6", - "pytest-dotenv>=0.5.2", - "pytest-mock>=3.14.0", - "pytest-asyncio>=0.24.0", - "pytest>=8.3.2", - "pytest-xdist>=3.6.1", - "pytest-cov>=5.0.0", - "tox>=4.0.0", - "chainlit>=1.1.306", -] - -[tool.rye.workspace] -members = [".", "core", "worker", "api", "docs", "core/examples/chatbot", "core/MegaParse", "worker/diff-assistant"] - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["src/backend"] - -[[tool.rye.sources]] -name = "pytorch" -url = "https://download.pytorch.org/whl/cpu" - -[tool.mypy] -disallow_untyped_defs = true -# Remove venv skip when integrated with pre-commit -exclude = ["_static", "build", "examples", "notebooks", "venv", ".venv"] -ignore_missing_imports = true -python_version = "3.11" - -[tool.ruff] -line-length = 88 -exclude = [".git", "__pycache__", ".mypy_cache", ".pytest_cache"] - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear -] -ignore = [ - "B904", - "B006", - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "C901", # too complex -] - -[tool.ruff.lint.isort] -order-by-type = true -relative-imports-order = "closest-to-furthest" -extra-standard-library = ["typing"] -section-order = [ - "future", - "standard-library", - "third-party", - "first-party", - "local-folder", -] -known-first-party = [] - -[tool.pytest.ini_options] -addopts = "--tb=short -ra -v" -asyncio_default_fixture_loop_scope = "session" -filterwarnings = ["ignore::DeprecationWarning"] -markers = [ - "slow: marks tests as slow (deselect with '-m \"not slow\"')", - "base: these tests require quivr-core with extra `base` to be installed", - "tika: these tests require a tika server to be running", - "unstructured: these tests require `unstructured` dependency", -] diff --git a/backend/supabase/.gitignore b/backend/supabase/.gitignore deleted file mode 100644 index a3ad88055..000000000 --- a/backend/supabase/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Supabase -.branches -.temp -.env diff --git a/backend/supabase/20240103191539_private.sql b/backend/supabase/20240103191539_private.sql deleted file mode 100644 index 070f94a55..000000000 --- a/backend/supabase/20240103191539_private.sql +++ /dev/null @@ -1,58 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS wrappers; - --- Create foreign data wrapper 'stripe_wrapper' if it doesn't exist -DO $$ -BEGIN -IF NOT EXISTS ( -SELECT 1 -FROM information_schema.foreign_data_wrappers -WHERE foreign_data_wrapper_name = 'stripe_wrapper' -) THEN -CREATE FOREIGN DATA WRAPPER stripe_wrapper -HANDLER stripe_fdw_handler; -END IF; -END $$; -^ --- Check if the server 'stripe_server' exists before creating it -DO $$ -BEGIN -IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname = 'stripe_server') THEN -CREATE SERVER stripe_server -FOREIGN DATA WRAPPER stripe_wrapper -OPTIONS ( -api_key 'pk_test_51NtDTIJglvQxkJ1HgOBKicXBZ9Ug9pIhOZz3Lkask6q5JPZRoRW49nmwW6Q7wjWHJgc89HbruUP7GJ0d5DlQYOQ200MkvXpFnV' -- Replace with your Stripe API key -); -END IF; -END $$; - --- Create foreign table 'public.customers' if it doesn't exist -DO $$ -BEGIN -IF NOT EXISTS ( -SELECT 1 -FROM information_schema.tables -WHERE table_name = 'customers' -) THEN -CREATE FOREIGN TABLE public.customers ( -id text, -email text, -name text, -description text, -created timestamp, -attrs jsonb -) -SERVER stripe_server -OPTIONS ( -OBJECT 'customers', -ROWID_COLUMN 'id' -); -END IF; -END $$; - - -create schema if not exists extensions; - -create table if not exists - extensions.wrappers_fdw_stats (); - -grant all on extensions.wrappers_fdw_stats to service_role; diff --git a/backend/supabase/config.toml b/backend/supabase/config.toml deleted file mode 100644 index 0c93177f7..000000000 --- a/backend/supabase/config.toml +++ /dev/null @@ -1,203 +0,0 @@ -# A string used to distinguish different Supabase projects on the same host. Defaults to the -# working directory name when running `supabase init`. -project_id = "secondbrain" - -[api] -enabled = true -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. `public` is always included. -schemas = ["public", "storage", "graphql_public", "vault"] -# Extra schemas to add to the search_path of every request. `public` is always included. -extra_search_path = ["public", "extensions", "stripe", "vault"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[api.tls] -enabled = false - -[db] -# Port to use for the local database URL. -port = 54322 -# Port used by db diff command to initialize the shadow database. -shadow_port = 54320 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 15 - -[db.pooler] -enabled = false -# Port to use for the local connection pooler. -port = 54329 -# Specifies when a server connection can be reused by other clients. -# Configure one of the supported pooler modes: `transaction`, `session`. -pool_mode = "transaction" -# How many server connections to allow per user/database pair. -default_pool_size = 20 -# Maximum number of client connections allowed. -max_client_conn = 100 - -[realtime] -enabled = true -# Bind realtime via either IPv4 or IPv6. (default: IPv4) -# ip_version = "IPv6" -# The maximum length in bytes of HTTP request headers. (default: 4096) -# max_header_length = 4096 - -[studio] -enabled = true -# Port to use for Supabase Studio. -port = 54323 -# External URL of the API server that frontend connects to. -api_url = "http://127.0.0.1" -# OpenAI API Key to use for Supabase AI in the Supabase Studio. -openai_api_key = "env(OPENAI_API_KEY)" - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -enabled = true -# Port to use for the email testing server web interface. -port = 54324 -# Uncomment to expose additional ports for testing user applications that send emails. -# smtp_port = 54325 -# pop3_port = 54326 - -[storage] -enabled = true -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -[storage.image_transformation] -enabled = false - -# Uncomment to configure local storage buckets -# [storage.buckets.images] -# public = false -# file_size_limit = "50MiB" -# allowed_mime_types = ["image/png", "image/jpeg"] -# objects_path = "./images" - -[auth] -enabled = true -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://127.0.0.1:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://127.0.0.1:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). -jwt_expiry = 3600 -# If disabled, the refresh token will never expire. -enable_refresh_token_rotation = true -# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. -# Requires enable_refresh_token_rotation = true. -refresh_token_reuse_interval = 10 -# Allow/disallow new user signups to your project. -enable_signup = true -# Allow/disallow anonymous sign-ins to your project. -enable_anonymous_sign_ins = false -# Allow/disallow testing manual linking of accounts -enable_manual_linking = false - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false -# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. -max_frequency = "1s" - -# Use a production-ready SMTP server -# [auth.email.smtp] -# host = "smtp.sendgrid.net" -# port = 587 -# user = "apikey" -# pass = "env(SENDGRID_API_KEY)" -# admin_email = "admin@email.com" -# sender_name = "Admin" - -# Uncomment to customize email template -# [auth.email.template.invite] -# subject = "You have been invited" -# content_path = "./supabase/templates/invite.html" - -[auth.sms] -# Allow/disallow new user signups via SMS to your project. -enable_signup = true -# If enabled, users need to confirm their phone number before signing in. -enable_confirmations = false -# Template for sending OTP to users -template = "Your code is {{ .Code }} ." -# Controls the minimum amount of time that must pass before sending another sms otp. -max_frequency = "5s" - -# Use pre-defined map of phone number to OTP for testing. -# [auth.sms.test_otp] -# 4152127777 = "123456" - -# Configure logged in session timeouts. -# [auth.sessions] -# Force log out after the specified duration. -# timebox = "24h" -# Force log out if the user has been inactive longer than the specified duration. -# inactivity_timeout = "8h" - -# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. -# [auth.hook.custom_access_token] -# enabled = true -# uri = "pg-functions:////" - -# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. -[auth.sms.twilio] -enabled = false -account_sid = "" -message_service_sid = "" -# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: -auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, -# `twitter`, `slack`, `spotify`, `workos`, `zoom`. -[auth.external.apple] -enabled = false -client_id = "" -# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: -secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" -# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. -skip_nonce_check = false - -[edge_runtime] -enabled = true -# Configure one of the supported request policies: `oneshot`, `per_worker`. -# Use `oneshot` for hot reload, or `per_worker` for load testing. -policy = "oneshot" -inspector_port = 8083 - -[analytics] -enabled = true -port = 54327 -# Configure one of the supported backends: `postgres`, `bigquery`. -backend = "postgres" - -# Experimental features may be deprecated any time -[experimental] -# Configures Postgres storage engine to use OrioleDB (S3) -orioledb_version = "" -# Configures S3 bucket URL, eg. .s3-.amazonaws.com -s3_host = "env(S3_HOST)" -# Configures S3 bucket region, eg. us-east-1 -s3_region = "env(S3_REGION)" -# Configures AWS_ACCESS_KEY_ID for S3 bucket -s3_access_key = "env(S3_ACCESS_KEY)" -# Configures AWS_SECRET_ACCESS_KEY for S3 bucket -s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/backend/supabase/functions/add-new-email/index.ts b/backend/supabase/functions/add-new-email/index.ts deleted file mode 100644 index 207274440..000000000 --- a/backend/supabase/functions/add-new-email/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* Instructions: -1. in .backend/supabase folder, create .env file with BEEHIIV_PUBLICATION_ID and BEEHIIV_API_KEY variables -2. cd into .backend ---- for the rest of these steps you will need your supabase project id which can be found in your console url: https://supabase.com/dashboard/project/ --- -3. run `supabase secrets set --env-file ./supabase/.env` to set the environment variables -4. run `supabase functions deploy add-new-email` to deploy the function -5. in the supabase console go to Database/Webhook and create new and point it to the edge function 'add-new-email'. You will have to add a new header Authorization: Bearer ${anon public key from Settings/API} to the webhook. -*/ - -import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; - -const publicationId = Deno.env.get("BEEHIIV_PUBLICATION_ID"); -const apiKey = Deno.env.get("BEEHIIV_API_KEY"); - -const url = `https://api.beehiiv.com/v2/publications/${publicationId}/subscriptions`; - -interface WebhookPayload { - type: "INSERT" | "UPDATE" | "DELETE"; - table: string; - record: { - id: string; - aud: string; - role: string; - email: string; - phone: null; - created_at: string; - }; -} - -serve( - async (req: { json: () => WebhookPayload | PromiseLike }) => { - if (!publicationId || !apiKey) { - throw new Error("Missing required environment variables"); - } - - const payload: WebhookPayload = await req.json(); - - if (payload.record.email) { - const requestBody = { - email: payload.record.email, - send_welcome_email: false, - utm_source: "quivr", - utm_medium: "organic", - referring_site: "https://chat.quivr.app", - }; - - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - Accept: "application/json", - }, - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - throw new Error( - `Error adding email to Beehiiv: ${JSON.stringify(response)}` - ); - } - - const responseBody = await response.json(); - return new Response(JSON.stringify(responseBody), { - status: response.status, - headers: { "Content-Type": "application/json" }, - }); - } - - throw new Error( - `No email address found in payload: ${JSON.stringify(payload)}` - ); - } -); diff --git a/backend/supabase/functions/phospho/index.ts b/backend/supabase/functions/phospho/index.ts deleted file mode 100644 index 5511b1489..000000000 --- a/backend/supabase/functions/phospho/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Follow this setup guide to integrate the Deno language server with your editor: -// https://deno.land/manual/getting_started/setup_your_environment -// This enables autocomplete, go to definition, etc. -import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; - -const phosphoApiKey = Deno.env.get("PHOSPHO_API_KEY"); -const phosphoProjectKey = Deno.env.get("PHOSPHO_PROJECT_KEY"); -const phosphoUrl = `https://api.phospho.ai/v2/log/${phosphoProjectKey}`; - -interface ChatHistoryPayload { - type: "UPDATE"; - table: string; - record: { - message_id: string; - chat_id: string; - user_message: string; - assistant: string; - message_time: string; - // other fields as necessary - }; -} - -serve( - async (req: { - json: () => ChatHistoryPayload | PromiseLike; - }) => { - if (!phosphoApiKey) { - throw new Error("Missing Phospho API key"); - } - - const payload: ChatHistoryPayload = await req.json(); - - if (payload.record.user_message && payload.type === "UPDATE") { - const phosphoPayload = { - batched_log_events: [ - { - task_id: payload.record.message_id, - session_id: payload.record.chat_id, - input: payload.record.user_message, - output: payload.record.assistant, - }, - ], - }; - - const response = await fetch(phosphoUrl, { - method: "POST", - headers: { - Authorization: `Bearer ${phosphoApiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(phosphoPayload), - }); - - if (!response.ok) { - throw new Error( - `Error sending chat data to Phospho: ${response.statusText}` - ); - } - - return new Response(null, { status: 200 }); - } - - return new Response("No new chat message detected", { status: 200 }); - } -); - -// Remember to replace 'your_phospho_project_id' with your actual Phospho project ID. diff --git a/backend/supabase/functions/telemetry/index.ts b/backend/supabase/functions/telemetry/index.ts deleted file mode 100644 index 00c6ef267..000000000 --- a/backend/supabase/functions/telemetry/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; - -Deno.serve(async (req: Request) => { - console.log("Received request"); - - const authHeader = req.headers.get("Authorization")!; - const supabaseClient = createClient( - Deno.env.get("SUPABASE_URL") ?? "", - Deno.env.get("SUPABASE_ANON_KEY") ?? "", - { global: { headers: { Authorization: authHeader } } } - ); - - // Parse the request body to get event data and the anonymous identifier - const { anonymous_identifier, event_name, event_data } = await req.json(); - - console.log( - `Parsed request body: ${JSON.stringify({ - anonymous_identifier, - event_name, - event_data, - })}` - ); - - // Insert the telemetry data along with the anonymous identifier into the Supabase table - const { data, error } = await supabaseClient - .from("telemetry") - .insert([{ anonymous_identifier, event_name, event_data }]); - - if (error) { - console.error(`Error inserting data into Supabase: ${error.message}`); - return new Response(JSON.stringify({ error: error.message }), { - status: 400, - headers: { - "Content-Type": "application/json", - }, - }); - } - - console.log("Successfully inserted data into Supabase"); - return new Response(JSON.stringify({ message: "Telemetry logged", data }), { - status: 200, - headers: { - "Content-Type": "application/json", - }, - }); -}); diff --git a/backend/supabase/functions/user/index.ts b/backend/supabase/functions/user/index.ts deleted file mode 100644 index 1e3a9c9a5..000000000 --- a/backend/supabase/functions/user/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* Instructions: - ---- for the rest of these steps you will need your supabase project id which can be found in your console url: https://supabase.com/dashboard/project/ --- -3. run `supabase secrets set --env-file ./supabase/.env` to set the environment variables -4. run `supabase functions deploy add-new-email` to deploy the function -5. in the supabase console go to Database/Webhook and create new and point it to the edge function 'add-new-email'. You will have to add a new header Authorization: Bearer ${anon public key from Settings/API} to the webhook. -*/ - -import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; - -const postHogApiKey = Deno.env.get("POSTHOG_API_KEY"); - -interface WebhookPayload { - type: "INSERT" | "UPDATE" | "DELETE"; - table: string; - record: { - id: string; - aud: string; - role: string; - email: string; - phone: null; - created_at: string; - }; -} - -const postHogUrl = "https://app.posthog.com/capture/"; - -serve( - async (req: { json: () => WebhookPayload | PromiseLike }) => { - if (!postHogApiKey) { - throw new Error("Missing PostHog API key"); - } - - const payload: WebhookPayload = await req.json(); - - if (payload.record.email && payload.type === "INSERT") { - const postHogPayload = { - event: "USER_SIGNED_UP", - api_key: postHogApiKey, - distinct_id: payload.record.id, - properties: { - email: payload.record.email, - }, - timestamp: payload.record.created_at, // assuming this is in ISO 8601 format - }; - - const response = await fetch(postHogUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(postHogPayload), - }); - - if (!response.ok) { - throw new Error( - `Error sending event to PostHog: ${response.statusText}` - ); - } - - return new Response(null, { status: 200 }); - } - - return new Response("No new user signup event detected", { status: 200 }); - } -); diff --git a/backend/supabase/migrations/20240103173626_init.sql b/backend/supabase/migrations/20240103173626_init.sql deleted file mode 100644 index 1dfc09f1a..000000000 --- a/backend/supabase/migrations/20240103173626_init.sql +++ /dev/null @@ -1,2099 +0,0 @@ -create extension if not exists "vector" with schema "public" ; - -create extension if not exists "wrappers" with schema "public"; - -create type "public"."brain_type_enum" as enum ('doc', 'api', 'composite'); - -create sequence "public"."summaries_id_seq"; - -create sequence "public"."vectors_id_seq"; - -create table "public"."api_brain_definition" ( - "brain_id" uuid, - "method" character varying(255), - "url" character varying(255), - "params" json, - "search_params" json, - "secrets" json -); - - -create table "public"."api_keys" ( - "key_id" uuid not null default gen_random_uuid(), - "user_id" uuid, - "api_key" text, - "creation_time" timestamp without time zone default CURRENT_TIMESTAMP, - "deleted_time" timestamp without time zone, - "is_active" boolean default true, - "name" text default 'API_KEY'::text, - "days" integer default 30, - "only_chat" boolean default false -); - - -create table "public"."brain_subscription_invitations" ( - "brain_id" uuid not null, - "email" character varying(255) not null, - "rights" character varying(255) -); - - -create table "public"."brains" ( - "brain_id" uuid not null default gen_random_uuid(), - "name" text, - "status" text, - "model" text, - "max_tokens" integer, - "temperature" double precision, - "description" text, - "prompt_id" uuid, - "retrieval_algorithm" text, - "last_update" timestamp without time zone default CURRENT_TIMESTAMP, - "brain_type" brain_type_enum default 'doc'::brain_type_enum -); - - -create table "public"."brains_users" ( - "brain_id" uuid not null, - "rights" character varying(255), - "default_brain" boolean, - "user_id" uuid -); - - -create table "public"."brains_vectors" ( - "brain_id" uuid not null, - "rights" character varying(255), - "file_sha1" text, - "vector_id" uuid -); - - -create table "public"."chat_history" ( - "message_id" uuid not null default uuid_generate_v4(), - "chat_id" uuid not null, - "user_message" text, - "assistant" text, - "message_time" timestamp without time zone default CURRENT_TIMESTAMP, - "brain_id" uuid, - "prompt_id" uuid -); - - -create table "public"."chats" ( - "chat_id" uuid not null default uuid_generate_v4(), - "user_id" uuid, - "creation_time" timestamp without time zone default CURRENT_TIMESTAMP, - "history" jsonb, - "chat_name" text -); - - -create table "public"."composite_brain_connections" ( - "composite_brain_id" uuid not null, - "connected_brain_id" uuid not null -); - - -create table "public"."knowledge" ( - "id" uuid not null default gen_random_uuid(), - "file_name" text, - "url" text, - "brain_id" uuid not null, - "extension" text not null -); - - -create table "public"."knowledge_vectors" ( - "knowledge_id" uuid not null, - "vector_id" uuid not null, - "embedding_model" text not null -); - - -create table "public"."migrations" ( - "name" character varying(255) not null, - "executed_at" timestamp with time zone default CURRENT_TIMESTAMP -); - - -create table "public"."notifications" ( - "id" uuid not null default gen_random_uuid(), - "datetime" timestamp without time zone default CURRENT_TIMESTAMP, - "chat_id" uuid, - "message" text, - "action" character varying(255) not null, - "status" character varying(255) not null -); - - -create table "public"."onboardings" ( - "user_id" uuid not null, - "onboarding_a" boolean not null default true, - "onboarding_b1" boolean not null default true, - "onboarding_b2" boolean not null default true, - "onboarding_b3" boolean not null default true, - "creation_time" timestamp without time zone default CURRENT_TIMESTAMP -); - - -create table "public"."prompts" ( - "id" uuid not null default uuid_generate_v4(), - "title" character varying(255), - "content" text, - "status" character varying(255) default 'private'::character varying -); - - -create table "public"."stats" ( - "time" timestamp without time zone, - "chat" boolean, - "embedding" boolean, - "details" text, - "metadata" jsonb, - "id" integer generated always as identity not null -); - - -create table "public"."summaries" ( - "id" bigint not null default nextval('summaries_id_seq'::regclass), - "content" text, - "metadata" jsonb, - "embedding" vector(1536), - "document_id" uuid -); - - -create table "public"."user_daily_usage" ( - "user_id" uuid not null, - "email" text, - "date" text not null, - "daily_requests_count" integer -); - - -create table "public"."user_identity" ( - "user_id" uuid not null, - "openai_api_key" character varying(255) -); - - -create table "public"."user_settings" ( - "user_id" uuid not null, - "models" jsonb default '["gpt-3.5-turbo-1106"]'::jsonb, - "daily_chat_credit" integer default 20, - "max_brains" integer default 3, - "max_brain_size" integer default 1000000 -); - - -create table "public"."users" ( - "id" uuid not null, - "email" text -); - - -create table "public"."users_old" ( - "user_id" uuid, - "email" text, - "date" text, - "requests_count" integer, - "supabase_id" uuid -); - - -create table "public"."vectors" ( - "id" uuid not null default uuid_generate_v4(), - "content" text, - "file_sha1" text, - "metadata" jsonb, - "embedding" vector(1536) -); - - -create table "public"."vectors_old" ( - "id" bigint not null default nextval('vectors_id_seq'::regclass), - "content" text, - "metadata" jsonb, - "embedding" vector(1536) -); - - -alter sequence "public"."summaries_id_seq" owned by "public"."summaries"."id"; - -alter sequence "public"."vectors_id_seq" owned by "public"."vectors_old"."id"; - -CREATE UNIQUE INDEX api_keys_api_key_key ON public.api_keys USING btree (api_key); - -CREATE UNIQUE INDEX api_keys_pkey ON public.api_keys USING btree (key_id); - -CREATE UNIQUE INDEX brain_subscription_invitations_pkey ON public.brain_subscription_invitations USING btree (brain_id, email); - -CREATE UNIQUE INDEX brains_pkey ON public.brains USING btree (brain_id); - -CREATE UNIQUE INDEX chat_history_pkey ON public.chat_history USING btree (chat_id, message_id); - -CREATE UNIQUE INDEX chats_pkey ON public.chats USING btree (chat_id); - -CREATE UNIQUE INDEX composite_brain_connections_pkey ON public.composite_brain_connections USING btree (composite_brain_id, connected_brain_id); - -CREATE UNIQUE INDEX knowledge_pkey ON public.knowledge USING btree (id); - -CREATE UNIQUE INDEX knowledge_vectors_pkey ON public.knowledge_vectors USING btree (knowledge_id, vector_id, embedding_model); - -CREATE UNIQUE INDEX migrations_pkey ON public.migrations USING btree (name); - -CREATE UNIQUE INDEX notifications_pkey ON public.notifications USING btree (id); - -CREATE UNIQUE INDEX onboardings_pkey ON public.onboardings USING btree (user_id); - -CREATE UNIQUE INDEX prompts_pkey ON public.prompts USING btree (id); - -CREATE UNIQUE INDEX stats_pkey ON public.stats USING btree (id); - -CREATE UNIQUE INDEX summaries_pkey ON public.summaries USING btree (id); - -CREATE UNIQUE INDEX user_daily_usage_pkey ON public.user_daily_usage USING btree (user_id, date); - -CREATE UNIQUE INDEX user_identity_pkey ON public.user_identity USING btree (user_id); - -CREATE UNIQUE INDEX user_settings_pkey ON public.user_settings USING btree (user_id); - -CREATE UNIQUE INDEX users_pkey ON public.users USING btree (id); - -CREATE UNIQUE INDEX vectors_pkey ON public.vectors_old USING btree (id); - -CREATE UNIQUE INDEX vectors_pkey1 ON public.vectors USING btree (id); - -alter table "public"."api_keys" add constraint "api_keys_pkey" PRIMARY KEY using index "api_keys_pkey"; - -alter table "public"."brain_subscription_invitations" add constraint "brain_subscription_invitations_pkey" PRIMARY KEY using index "brain_subscription_invitations_pkey"; - -alter table "public"."brains" add constraint "brains_pkey" PRIMARY KEY using index "brains_pkey"; - -alter table "public"."chat_history" add constraint "chat_history_pkey" PRIMARY KEY using index "chat_history_pkey"; - -alter table "public"."chats" add constraint "chats_pkey" PRIMARY KEY using index "chats_pkey"; - -alter table "public"."composite_brain_connections" add constraint "composite_brain_connections_pkey" PRIMARY KEY using index "composite_brain_connections_pkey"; - -alter table "public"."knowledge" add constraint "knowledge_pkey" PRIMARY KEY using index "knowledge_pkey"; - -alter table "public"."knowledge_vectors" add constraint "knowledge_vectors_pkey" PRIMARY KEY using index "knowledge_vectors_pkey"; - -alter table "public"."migrations" add constraint "migrations_pkey" PRIMARY KEY using index "migrations_pkey"; - -alter table "public"."notifications" add constraint "notifications_pkey" PRIMARY KEY using index "notifications_pkey"; - -alter table "public"."onboardings" add constraint "onboardings_pkey" PRIMARY KEY using index "onboardings_pkey"; - -alter table "public"."prompts" add constraint "prompts_pkey" PRIMARY KEY using index "prompts_pkey"; - -alter table "public"."stats" add constraint "stats_pkey" PRIMARY KEY using index "stats_pkey"; - -alter table "public"."summaries" add constraint "summaries_pkey" PRIMARY KEY using index "summaries_pkey"; - -alter table "public"."user_daily_usage" add constraint "user_daily_usage_pkey" PRIMARY KEY using index "user_daily_usage_pkey"; - -alter table "public"."user_identity" add constraint "user_identity_pkey" PRIMARY KEY using index "user_identity_pkey"; - -alter table "public"."user_settings" add constraint "user_settings_pkey" PRIMARY KEY using index "user_settings_pkey"; - -alter table "public"."users" add constraint "users_pkey" PRIMARY KEY using index "users_pkey"; - -alter table "public"."vectors" add constraint "vectors_pkey1" PRIMARY KEY using index "vectors_pkey1"; - -alter table "public"."vectors_old" add constraint "vectors_pkey" PRIMARY KEY using index "vectors_pkey"; - -alter table "public"."api_brain_definition" add constraint "api_brain_definition_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."api_brain_definition" validate constraint "api_brain_definition_brain_id_fkey"; - -alter table "public"."api_brain_definition" add constraint "api_brain_definition_method_check" CHECK (((method)::text = ANY ((ARRAY['GET'::character varying, 'POST'::character varying, 'PUT'::character varying, 'DELETE'::character varying])::text[]))) not valid; - -alter table "public"."api_brain_definition" validate constraint "api_brain_definition_method_check"; - -alter table "public"."api_keys" add constraint "api_keys_api_key_key" UNIQUE using index "api_keys_api_key_key"; - -alter table "public"."brain_subscription_invitations" add constraint "brain_subscription_invitations_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."brain_subscription_invitations" validate constraint "brain_subscription_invitations_brain_id_fkey"; - -alter table "public"."brains" add constraint "brains_prompt_id_fkey" FOREIGN KEY (prompt_id) REFERENCES prompts(id) not valid; - -alter table "public"."brains" validate constraint "brains_prompt_id_fkey"; - -alter table "public"."brains_users" add constraint "brains_users_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."brains_users" validate constraint "brains_users_brain_id_fkey"; - -alter table "public"."brains_users" add constraint "brains_users_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid; - -alter table "public"."brains_users" validate constraint "brains_users_user_id_fkey"; - -alter table "public"."brains_vectors" add constraint "brains_vectors_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."brains_vectors" validate constraint "brains_vectors_brain_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."chat_history" validate constraint "chat_history_brain_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_chat_id_fkey" FOREIGN KEY (chat_id) REFERENCES chats(chat_id) not valid; - -alter table "public"."chat_history" validate constraint "chat_history_chat_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_prompt_id_fkey" FOREIGN KEY (prompt_id) REFERENCES prompts(id) not valid; - -alter table "public"."chat_history" validate constraint "chat_history_prompt_id_fkey"; - -alter table "public"."chats" add constraint "chats_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid; - -alter table "public"."chats" validate constraint "chats_user_id_fkey"; - -alter table "public"."composite_brain_connections" add constraint "composite_brain_connections_check" CHECK ((composite_brain_id <> connected_brain_id)) not valid; - -alter table "public"."composite_brain_connections" validate constraint "composite_brain_connections_check"; - -alter table "public"."composite_brain_connections" add constraint "composite_brain_connections_composite_brain_id_fkey" FOREIGN KEY (composite_brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."composite_brain_connections" validate constraint "composite_brain_connections_composite_brain_id_fkey"; - -alter table "public"."composite_brain_connections" add constraint "composite_brain_connections_connected_brain_id_fkey" FOREIGN KEY (connected_brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."composite_brain_connections" validate constraint "composite_brain_connections_connected_brain_id_fkey"; - -alter table "public"."knowledge" add constraint "knowledge_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) not valid; - -alter table "public"."knowledge" validate constraint "knowledge_brain_id_fkey"; - -alter table "public"."knowledge" add constraint "knowledge_check" CHECK ((((file_name IS NOT NULL) AND (url IS NULL)) OR ((file_name IS NULL) AND (url IS NOT NULL)))) not valid; - -alter table "public"."knowledge" validate constraint "knowledge_check"; - -alter table "public"."knowledge_vectors" add constraint "knowledge_vectors_knowledge_id_fkey" FOREIGN KEY (knowledge_id) REFERENCES knowledge(id) not valid; - -alter table "public"."knowledge_vectors" validate constraint "knowledge_vectors_knowledge_id_fkey"; - -alter table "public"."notifications" add constraint "notifications_chat_id_fkey" FOREIGN KEY (chat_id) REFERENCES chats(chat_id) not valid; - -alter table "public"."notifications" validate constraint "notifications_chat_id_fkey"; - -alter table "public"."onboardings" add constraint "onboardings_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid; - -alter table "public"."onboardings" validate constraint "onboardings_user_id_fkey"; - -alter table "public"."user_daily_usage" add constraint "user_daily_usage_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid; - -alter table "public"."user_daily_usage" validate constraint "user_daily_usage_user_id_fkey"; - -alter table "public"."users" add constraint "users_id_fkey" FOREIGN KEY (id) REFERENCES auth.users(id) not valid; - -alter table "public"."users" validate constraint "users_id_fkey"; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.create_user_onboarding() - RETURNS trigger - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -BEGIN - INSERT INTO public.onboardings (user_id) - VALUES (NEW.id); - RETURN NEW; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.delete_secret(secret_name text) - RETURNS text - LANGUAGE plpgsql - SECURITY DEFINER - SET search_path TO 'public' -AS $function$ -declare - deleted_rows int; -begin - delete from vault.decrypted_secrets where name = secret_name; - get diagnostics deleted_rows = row_count; - if deleted_rows = 0 then - return false; - else - return true; - end if; -end; -$function$ -; - -CREATE OR REPLACE FUNCTION public.get_premium_user(input_email text) - RETURNS TABLE(email text) - LANGUAGE plpgsql -AS $function$ -BEGIN -RETURN QUERY -SELECT c.email -FROM stripe.customers c -WHERE c.email = input_email; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.get_user_email_by_user_id(user_id uuid) - RETURNS TABLE(email text) - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -BEGIN - RETURN QUERY SELECT au.email::text FROM auth.users au WHERE au.id = user_id; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.handle_new_user() - RETURNS trigger - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -BEGIN - INSERT INTO public.users (id, email) - VALUES (NEW.id, NEW.email); - RETURN NEW; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.insert_secret(name text, secret text) - RETURNS uuid - LANGUAGE plpgsql - SECURITY DEFINER - SET search_path TO 'public' -AS $function$ -begin - return vault.create_secret(secret, name); -end; -$function$ -; - -CREATE OR REPLACE FUNCTION public.match_vectors(query_embedding vector, match_count integer, p_brain_id uuid) - RETURNS TABLE(id uuid, brain_id uuid, content text, metadata jsonb, embedding vector, similarity double precision) - LANGUAGE plpgsql -AS $function$ -#variable_conflict use_column -BEGIN - RETURN QUERY - SELECT - vectors.id, - brains_vectors.brain_id, - vectors.content, - vectors.metadata, - vectors.embedding, - 1 - (vectors.embedding <=> query_embedding) AS similarity - FROM - vectors - INNER JOIN - brains_vectors ON vectors.id = brains_vectors.vector_id - WHERE brains_vectors.brain_id = p_brain_id - ORDER BY - vectors.embedding <=> query_embedding - LIMIT match_count; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.read_secret(secret_name text) - RETURNS text - LANGUAGE plpgsql - SECURITY DEFINER - SET search_path TO 'public' -AS $function$ -declare - secret text; -begin - select decrypted_secret from vault.decrypted_secrets where name = - secret_name into secret; - return secret; -end; -$function$ -; - -CREATE OR REPLACE FUNCTION public.update_max_brains() - RETURNS trigger - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -DECLARE - userEmail TEXT; -BEGIN - SELECT email INTO userEmail FROM auth.users WHERE id = NEW.user_id; - - IF userEmail LIKE '%@theodo.fr' THEN - -- Ensure the models column is initialized as an array if null - IF NEW.models IS NULL THEN - NEW.models := '[]'::jsonb; - END IF; - - -- Add gpt-4 if not present - IF NOT NEW.models ? 'gpt-4' THEN - NEW.models := NEW.models || '["gpt-4"]'::jsonb; - END IF; - - -- Add gpt-3.5-turbo if not present - IF NOT NEW.models ? 'gpt-3.5-turbo' THEN - NEW.models := NEW.models || '["gpt-3.5-turbo"]'::jsonb; - END IF; - - -- Add gpt-3.5-turbo-16k if not present - IF NOT NEW.models ? 'gpt-3.5-turbo-16k' THEN - NEW.models := NEW.models || '["gpt-3.5-turbo-16k"]'::jsonb; - END IF; - - UPDATE user_settings - SET - max_brains = 30, - max_brain_size = 10000000, - models = NEW.models - WHERE user_id = NEW.user_id; - END IF; - - RETURN NULL; -- for AFTER triggers, the return value is ignored -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.update_max_brains_theodo() - RETURNS trigger - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -DECLARE - userEmail TEXT; - allowedDomains TEXT[] := ARRAY['@theodo.fr', '@theodo.com', '@theodo.co.uk', '@bam.tech', '@padok.fr', '@sicara.fr', '@hokla.com', '@sipios.com']; -BEGIN - SELECT email INTO userEmail FROM auth.users WHERE id = NEW.user_id; - - IF userEmail LIKE ANY(allowedDomains) THEN - -- Ensure the models column is initialized as an array if null - IF NEW.models IS NULL THEN - NEW.models := '[]'::jsonb; - END IF; - - -- Add gpt-4 if not present - IF NOT NEW.models ? 'gpt-4' THEN - NEW.models := NEW.models || '["gpt-4"]'::jsonb; - END IF; - - -- Add gpt-3.5-turbo if not present - IF NOT NEW.models ? 'gpt-3.5-turbo' THEN - NEW.models := NEW.models || '["gpt-3.5-turbo"]'::jsonb; - END IF; - - -- Add gpt-3.5-turbo-16k if not present - IF NOT NEW.models ? 'gpt-3.5-turbo-16k' THEN - NEW.models := NEW.models || '["gpt-3.5-turbo-16k"]'::jsonb; - END IF; - - UPDATE user_settings - SET - max_brains = 30, - max_brain_size = 100000000, - - models = NEW.models - WHERE user_id = NEW.user_id; - END IF; - - RETURN NULL; -- for AFTER triggers, the return value is ignored -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.update_user_settings() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ -BEGIN - IF NEW.email LIKE '%@theodo.fr' THEN - -- This checks if the models key is present and is of type jsonb array, - -- if not it initializes it with an empty array. - IF NEW.models IS NULL OR NOT jsonb_typeof(NEW.models) = 'array' THEN - NEW.models := '[]'::jsonb; - END IF; - - -- Append new values to the JSONB array. - -- This does not check for duplicates, so you might get repeated values. - NEW.models := NEW.models || '["gpt-4", "gpt-3.5-turbo"]'::jsonb; - END IF; - RETURN NEW; -END; -$function$ -; - -grant delete on table "public"."api_brain_definition" to "anon"; - -grant insert on table "public"."api_brain_definition" to "anon"; - -grant references on table "public"."api_brain_definition" to "anon"; - -grant select on table "public"."api_brain_definition" to "anon"; - -grant trigger on table "public"."api_brain_definition" to "anon"; - -grant truncate on table "public"."api_brain_definition" to "anon"; - -grant update on table "public"."api_brain_definition" to "anon"; - -grant delete on table "public"."api_brain_definition" to "authenticated"; - -grant insert on table "public"."api_brain_definition" to "authenticated"; - -grant references on table "public"."api_brain_definition" to "authenticated"; - -grant select on table "public"."api_brain_definition" to "authenticated"; - -grant trigger on table "public"."api_brain_definition" to "authenticated"; - -grant truncate on table "public"."api_brain_definition" to "authenticated"; - -grant update on table "public"."api_brain_definition" to "authenticated"; - -grant delete on table "public"."api_brain_definition" to "service_role"; - -grant insert on table "public"."api_brain_definition" to "service_role"; - -grant references on table "public"."api_brain_definition" to "service_role"; - -grant select on table "public"."api_brain_definition" to "service_role"; - -grant trigger on table "public"."api_brain_definition" to "service_role"; - -grant truncate on table "public"."api_brain_definition" to "service_role"; - -grant update on table "public"."api_brain_definition" to "service_role"; - -grant delete on table "public"."api_keys" to "anon"; - -grant insert on table "public"."api_keys" to "anon"; - -grant references on table "public"."api_keys" to "anon"; - -grant select on table "public"."api_keys" to "anon"; - -grant trigger on table "public"."api_keys" to "anon"; - -grant truncate on table "public"."api_keys" to "anon"; - -grant update on table "public"."api_keys" to "anon"; - -grant delete on table "public"."api_keys" to "authenticated"; - -grant insert on table "public"."api_keys" to "authenticated"; - -grant references on table "public"."api_keys" to "authenticated"; - -grant select on table "public"."api_keys" to "authenticated"; - -grant trigger on table "public"."api_keys" to "authenticated"; - -grant truncate on table "public"."api_keys" to "authenticated"; - -grant update on table "public"."api_keys" to "authenticated"; - -grant delete on table "public"."api_keys" to "service_role"; - -grant insert on table "public"."api_keys" to "service_role"; - -grant references on table "public"."api_keys" to "service_role"; - -grant select on table "public"."api_keys" to "service_role"; - -grant trigger on table "public"."api_keys" to "service_role"; - -grant truncate on table "public"."api_keys" to "service_role"; - -grant update on table "public"."api_keys" to "service_role"; - -grant delete on table "public"."brain_subscription_invitations" to "anon"; - -grant insert on table "public"."brain_subscription_invitations" to "anon"; - -grant references on table "public"."brain_subscription_invitations" to "anon"; - -grant select on table "public"."brain_subscription_invitations" to "anon"; - -grant trigger on table "public"."brain_subscription_invitations" to "anon"; - -grant truncate on table "public"."brain_subscription_invitations" to "anon"; - -grant update on table "public"."brain_subscription_invitations" to "anon"; - -grant delete on table "public"."brain_subscription_invitations" to "authenticated"; - -grant insert on table "public"."brain_subscription_invitations" to "authenticated"; - -grant references on table "public"."brain_subscription_invitations" to "authenticated"; - -grant select on table "public"."brain_subscription_invitations" to "authenticated"; - -grant trigger on table "public"."brain_subscription_invitations" to "authenticated"; - -grant truncate on table "public"."brain_subscription_invitations" to "authenticated"; - -grant update on table "public"."brain_subscription_invitations" to "authenticated"; - -grant delete on table "public"."brain_subscription_invitations" to "service_role"; - -grant insert on table "public"."brain_subscription_invitations" to "service_role"; - -grant references on table "public"."brain_subscription_invitations" to "service_role"; - -grant select on table "public"."brain_subscription_invitations" to "service_role"; - -grant trigger on table "public"."brain_subscription_invitations" to "service_role"; - -grant truncate on table "public"."brain_subscription_invitations" to "service_role"; - -grant update on table "public"."brain_subscription_invitations" to "service_role"; - -grant delete on table "public"."brains" to "anon"; - -grant insert on table "public"."brains" to "anon"; - -grant references on table "public"."brains" to "anon"; - -grant select on table "public"."brains" to "anon"; - -grant trigger on table "public"."brains" to "anon"; - -grant truncate on table "public"."brains" to "anon"; - -grant update on table "public"."brains" to "anon"; - -grant delete on table "public"."brains" to "authenticated"; - -grant insert on table "public"."brains" to "authenticated"; - -grant references on table "public"."brains" to "authenticated"; - -grant select on table "public"."brains" to "authenticated"; - -grant trigger on table "public"."brains" to "authenticated"; - -grant truncate on table "public"."brains" to "authenticated"; - -grant update on table "public"."brains" to "authenticated"; - -grant delete on table "public"."brains" to "service_role"; - -grant insert on table "public"."brains" to "service_role"; - -grant references on table "public"."brains" to "service_role"; - -grant select on table "public"."brains" to "service_role"; - -grant trigger on table "public"."brains" to "service_role"; - -grant truncate on table "public"."brains" to "service_role"; - -grant update on table "public"."brains" to "service_role"; - -grant delete on table "public"."brains_users" to "anon"; - -grant insert on table "public"."brains_users" to "anon"; - -grant references on table "public"."brains_users" to "anon"; - -grant select on table "public"."brains_users" to "anon"; - -grant trigger on table "public"."brains_users" to "anon"; - -grant truncate on table "public"."brains_users" to "anon"; - -grant update on table "public"."brains_users" to "anon"; - -grant delete on table "public"."brains_users" to "authenticated"; - -grant insert on table "public"."brains_users" to "authenticated"; - -grant references on table "public"."brains_users" to "authenticated"; - -grant select on table "public"."brains_users" to "authenticated"; - -grant trigger on table "public"."brains_users" to "authenticated"; - -grant truncate on table "public"."brains_users" to "authenticated"; - -grant update on table "public"."brains_users" to "authenticated"; - -grant delete on table "public"."brains_users" to "service_role"; - -grant insert on table "public"."brains_users" to "service_role"; - -grant references on table "public"."brains_users" to "service_role"; - -grant select on table "public"."brains_users" to "service_role"; - -grant trigger on table "public"."brains_users" to "service_role"; - -grant truncate on table "public"."brains_users" to "service_role"; - -grant update on table "public"."brains_users" to "service_role"; - -grant delete on table "public"."brains_vectors" to "anon"; - -grant insert on table "public"."brains_vectors" to "anon"; - -grant references on table "public"."brains_vectors" to "anon"; - -grant select on table "public"."brains_vectors" to "anon"; - -grant trigger on table "public"."brains_vectors" to "anon"; - -grant truncate on table "public"."brains_vectors" to "anon"; - -grant update on table "public"."brains_vectors" to "anon"; - -grant delete on table "public"."brains_vectors" to "authenticated"; - -grant insert on table "public"."brains_vectors" to "authenticated"; - -grant references on table "public"."brains_vectors" to "authenticated"; - -grant select on table "public"."brains_vectors" to "authenticated"; - -grant trigger on table "public"."brains_vectors" to "authenticated"; - -grant truncate on table "public"."brains_vectors" to "authenticated"; - -grant update on table "public"."brains_vectors" to "authenticated"; - -grant delete on table "public"."brains_vectors" to "service_role"; - -grant insert on table "public"."brains_vectors" to "service_role"; - -grant references on table "public"."brains_vectors" to "service_role"; - -grant select on table "public"."brains_vectors" to "service_role"; - -grant trigger on table "public"."brains_vectors" to "service_role"; - -grant truncate on table "public"."brains_vectors" to "service_role"; - -grant update on table "public"."brains_vectors" to "service_role"; - -grant delete on table "public"."chat_history" to "anon"; - -grant insert on table "public"."chat_history" to "anon"; - -grant references on table "public"."chat_history" to "anon"; - -grant select on table "public"."chat_history" to "anon"; - -grant trigger on table "public"."chat_history" to "anon"; - -grant truncate on table "public"."chat_history" to "anon"; - -grant update on table "public"."chat_history" to "anon"; - -grant delete on table "public"."chat_history" to "authenticated"; - -grant insert on table "public"."chat_history" to "authenticated"; - -grant references on table "public"."chat_history" to "authenticated"; - -grant select on table "public"."chat_history" to "authenticated"; - -grant trigger on table "public"."chat_history" to "authenticated"; - -grant truncate on table "public"."chat_history" to "authenticated"; - -grant update on table "public"."chat_history" to "authenticated"; - -grant delete on table "public"."chat_history" to "service_role"; - -grant insert on table "public"."chat_history" to "service_role"; - -grant references on table "public"."chat_history" to "service_role"; - -grant select on table "public"."chat_history" to "service_role"; - -grant trigger on table "public"."chat_history" to "service_role"; - -grant truncate on table "public"."chat_history" to "service_role"; - -grant update on table "public"."chat_history" to "service_role"; - -grant delete on table "public"."chats" to "anon"; - -grant insert on table "public"."chats" to "anon"; - -grant references on table "public"."chats" to "anon"; - -grant select on table "public"."chats" to "anon"; - -grant trigger on table "public"."chats" to "anon"; - -grant truncate on table "public"."chats" to "anon"; - -grant update on table "public"."chats" to "anon"; - -grant delete on table "public"."chats" to "authenticated"; - -grant insert on table "public"."chats" to "authenticated"; - -grant references on table "public"."chats" to "authenticated"; - -grant select on table "public"."chats" to "authenticated"; - -grant trigger on table "public"."chats" to "authenticated"; - -grant truncate on table "public"."chats" to "authenticated"; - -grant update on table "public"."chats" to "authenticated"; - -grant delete on table "public"."chats" to "service_role"; - -grant insert on table "public"."chats" to "service_role"; - -grant references on table "public"."chats" to "service_role"; - -grant select on table "public"."chats" to "service_role"; - -grant trigger on table "public"."chats" to "service_role"; - -grant truncate on table "public"."chats" to "service_role"; - -grant update on table "public"."chats" to "service_role"; - -grant delete on table "public"."composite_brain_connections" to "anon"; - -grant insert on table "public"."composite_brain_connections" to "anon"; - -grant references on table "public"."composite_brain_connections" to "anon"; - -grant select on table "public"."composite_brain_connections" to "anon"; - -grant trigger on table "public"."composite_brain_connections" to "anon"; - -grant truncate on table "public"."composite_brain_connections" to "anon"; - -grant update on table "public"."composite_brain_connections" to "anon"; - -grant delete on table "public"."composite_brain_connections" to "authenticated"; - -grant insert on table "public"."composite_brain_connections" to "authenticated"; - -grant references on table "public"."composite_brain_connections" to "authenticated"; - -grant select on table "public"."composite_brain_connections" to "authenticated"; - -grant trigger on table "public"."composite_brain_connections" to "authenticated"; - -grant truncate on table "public"."composite_brain_connections" to "authenticated"; - -grant update on table "public"."composite_brain_connections" to "authenticated"; - -grant delete on table "public"."composite_brain_connections" to "service_role"; - -grant insert on table "public"."composite_brain_connections" to "service_role"; - -grant references on table "public"."composite_brain_connections" to "service_role"; - -grant select on table "public"."composite_brain_connections" to "service_role"; - -grant trigger on table "public"."composite_brain_connections" to "service_role"; - -grant truncate on table "public"."composite_brain_connections" to "service_role"; - -grant update on table "public"."composite_brain_connections" to "service_role"; - -grant delete on table "public"."knowledge" to "anon"; - -grant insert on table "public"."knowledge" to "anon"; - -grant references on table "public"."knowledge" to "anon"; - -grant select on table "public"."knowledge" to "anon"; - -grant trigger on table "public"."knowledge" to "anon"; - -grant truncate on table "public"."knowledge" to "anon"; - -grant update on table "public"."knowledge" to "anon"; - -grant delete on table "public"."knowledge" to "authenticated"; - -grant insert on table "public"."knowledge" to "authenticated"; - -grant references on table "public"."knowledge" to "authenticated"; - -grant select on table "public"."knowledge" to "authenticated"; - -grant trigger on table "public"."knowledge" to "authenticated"; - -grant truncate on table "public"."knowledge" to "authenticated"; - -grant update on table "public"."knowledge" to "authenticated"; - -grant delete on table "public"."knowledge" to "service_role"; - -grant insert on table "public"."knowledge" to "service_role"; - -grant references on table "public"."knowledge" to "service_role"; - -grant select on table "public"."knowledge" to "service_role"; - -grant trigger on table "public"."knowledge" to "service_role"; - -grant truncate on table "public"."knowledge" to "service_role"; - -grant update on table "public"."knowledge" to "service_role"; - -grant delete on table "public"."knowledge_vectors" to "anon"; - -grant insert on table "public"."knowledge_vectors" to "anon"; - -grant references on table "public"."knowledge_vectors" to "anon"; - -grant select on table "public"."knowledge_vectors" to "anon"; - -grant trigger on table "public"."knowledge_vectors" to "anon"; - -grant truncate on table "public"."knowledge_vectors" to "anon"; - -grant update on table "public"."knowledge_vectors" to "anon"; - -grant delete on table "public"."knowledge_vectors" to "authenticated"; - -grant insert on table "public"."knowledge_vectors" to "authenticated"; - -grant references on table "public"."knowledge_vectors" to "authenticated"; - -grant select on table "public"."knowledge_vectors" to "authenticated"; - -grant trigger on table "public"."knowledge_vectors" to "authenticated"; - -grant truncate on table "public"."knowledge_vectors" to "authenticated"; - -grant update on table "public"."knowledge_vectors" to "authenticated"; - -grant delete on table "public"."knowledge_vectors" to "service_role"; - -grant insert on table "public"."knowledge_vectors" to "service_role"; - -grant references on table "public"."knowledge_vectors" to "service_role"; - -grant select on table "public"."knowledge_vectors" to "service_role"; - -grant trigger on table "public"."knowledge_vectors" to "service_role"; - -grant truncate on table "public"."knowledge_vectors" to "service_role"; - -grant update on table "public"."knowledge_vectors" to "service_role"; - -grant delete on table "public"."migrations" to "anon"; - -grant insert on table "public"."migrations" to "anon"; - -grant references on table "public"."migrations" to "anon"; - -grant select on table "public"."migrations" to "anon"; - -grant trigger on table "public"."migrations" to "anon"; - -grant truncate on table "public"."migrations" to "anon"; - -grant update on table "public"."migrations" to "anon"; - -grant delete on table "public"."migrations" to "authenticated"; - -grant insert on table "public"."migrations" to "authenticated"; - -grant references on table "public"."migrations" to "authenticated"; - -grant select on table "public"."migrations" to "authenticated"; - -grant trigger on table "public"."migrations" to "authenticated"; - -grant truncate on table "public"."migrations" to "authenticated"; - -grant update on table "public"."migrations" to "authenticated"; - -grant delete on table "public"."migrations" to "service_role"; - -grant insert on table "public"."migrations" to "service_role"; - -grant references on table "public"."migrations" to "service_role"; - -grant select on table "public"."migrations" to "service_role"; - -grant trigger on table "public"."migrations" to "service_role"; - -grant truncate on table "public"."migrations" to "service_role"; - -grant update on table "public"."migrations" to "service_role"; - -grant delete on table "public"."notifications" to "anon"; - -grant insert on table "public"."notifications" to "anon"; - -grant references on table "public"."notifications" to "anon"; - -grant select on table "public"."notifications" to "anon"; - -grant trigger on table "public"."notifications" to "anon"; - -grant truncate on table "public"."notifications" to "anon"; - -grant update on table "public"."notifications" to "anon"; - -grant delete on table "public"."notifications" to "authenticated"; - -grant insert on table "public"."notifications" to "authenticated"; - -grant references on table "public"."notifications" to "authenticated"; - -grant select on table "public"."notifications" to "authenticated"; - -grant trigger on table "public"."notifications" to "authenticated"; - -grant truncate on table "public"."notifications" to "authenticated"; - -grant update on table "public"."notifications" to "authenticated"; - -grant delete on table "public"."notifications" to "service_role"; - -grant insert on table "public"."notifications" to "service_role"; - -grant references on table "public"."notifications" to "service_role"; - -grant select on table "public"."notifications" to "service_role"; - -grant trigger on table "public"."notifications" to "service_role"; - -grant truncate on table "public"."notifications" to "service_role"; - -grant update on table "public"."notifications" to "service_role"; - -grant delete on table "public"."onboardings" to "anon"; - -grant insert on table "public"."onboardings" to "anon"; - -grant references on table "public"."onboardings" to "anon"; - -grant select on table "public"."onboardings" to "anon"; - -grant trigger on table "public"."onboardings" to "anon"; - -grant truncate on table "public"."onboardings" to "anon"; - -grant update on table "public"."onboardings" to "anon"; - -grant delete on table "public"."onboardings" to "authenticated"; - -grant insert on table "public"."onboardings" to "authenticated"; - -grant references on table "public"."onboardings" to "authenticated"; - -grant select on table "public"."onboardings" to "authenticated"; - -grant trigger on table "public"."onboardings" to "authenticated"; - -grant truncate on table "public"."onboardings" to "authenticated"; - -grant update on table "public"."onboardings" to "authenticated"; - -grant delete on table "public"."onboardings" to "service_role"; - -grant insert on table "public"."onboardings" to "service_role"; - -grant references on table "public"."onboardings" to "service_role"; - -grant select on table "public"."onboardings" to "service_role"; - -grant trigger on table "public"."onboardings" to "service_role"; - -grant truncate on table "public"."onboardings" to "service_role"; - -grant update on table "public"."onboardings" to "service_role"; - -grant delete on table "public"."prompts" to "anon"; - -grant insert on table "public"."prompts" to "anon"; - -grant references on table "public"."prompts" to "anon"; - -grant select on table "public"."prompts" to "anon"; - -grant trigger on table "public"."prompts" to "anon"; - -grant truncate on table "public"."prompts" to "anon"; - -grant update on table "public"."prompts" to "anon"; - -grant delete on table "public"."prompts" to "authenticated"; - -grant insert on table "public"."prompts" to "authenticated"; - -grant references on table "public"."prompts" to "authenticated"; - -grant select on table "public"."prompts" to "authenticated"; - -grant trigger on table "public"."prompts" to "authenticated"; - -grant truncate on table "public"."prompts" to "authenticated"; - -grant update on table "public"."prompts" to "authenticated"; - -grant delete on table "public"."prompts" to "service_role"; - -grant insert on table "public"."prompts" to "service_role"; - -grant references on table "public"."prompts" to "service_role"; - -grant select on table "public"."prompts" to "service_role"; - -grant trigger on table "public"."prompts" to "service_role"; - -grant truncate on table "public"."prompts" to "service_role"; - -grant update on table "public"."prompts" to "service_role"; - -grant delete on table "public"."stats" to "anon"; - -grant insert on table "public"."stats" to "anon"; - -grant references on table "public"."stats" to "anon"; - -grant select on table "public"."stats" to "anon"; - -grant trigger on table "public"."stats" to "anon"; - -grant truncate on table "public"."stats" to "anon"; - -grant update on table "public"."stats" to "anon"; - -grant delete on table "public"."stats" to "authenticated"; - -grant insert on table "public"."stats" to "authenticated"; - -grant references on table "public"."stats" to "authenticated"; - -grant select on table "public"."stats" to "authenticated"; - -grant trigger on table "public"."stats" to "authenticated"; - -grant truncate on table "public"."stats" to "authenticated"; - -grant update on table "public"."stats" to "authenticated"; - -grant delete on table "public"."stats" to "service_role"; - -grant insert on table "public"."stats" to "service_role"; - -grant references on table "public"."stats" to "service_role"; - -grant select on table "public"."stats" to "service_role"; - -grant trigger on table "public"."stats" to "service_role"; - -grant truncate on table "public"."stats" to "service_role"; - -grant update on table "public"."stats" to "service_role"; - -grant delete on table "public"."summaries" to "anon"; - -grant insert on table "public"."summaries" to "anon"; - -grant references on table "public"."summaries" to "anon"; - -grant select on table "public"."summaries" to "anon"; - -grant trigger on table "public"."summaries" to "anon"; - -grant truncate on table "public"."summaries" to "anon"; - -grant update on table "public"."summaries" to "anon"; - -grant delete on table "public"."summaries" to "authenticated"; - -grant insert on table "public"."summaries" to "authenticated"; - -grant references on table "public"."summaries" to "authenticated"; - -grant select on table "public"."summaries" to "authenticated"; - -grant trigger on table "public"."summaries" to "authenticated"; - -grant truncate on table "public"."summaries" to "authenticated"; - -grant update on table "public"."summaries" to "authenticated"; - -grant delete on table "public"."summaries" to "service_role"; - -grant insert on table "public"."summaries" to "service_role"; - -grant references on table "public"."summaries" to "service_role"; - -grant select on table "public"."summaries" to "service_role"; - -grant trigger on table "public"."summaries" to "service_role"; - -grant truncate on table "public"."summaries" to "service_role"; - -grant update on table "public"."summaries" to "service_role"; - -grant delete on table "public"."user_daily_usage" to "anon"; - -grant insert on table "public"."user_daily_usage" to "anon"; - -grant references on table "public"."user_daily_usage" to "anon"; - -grant select on table "public"."user_daily_usage" to "anon"; - -grant trigger on table "public"."user_daily_usage" to "anon"; - -grant truncate on table "public"."user_daily_usage" to "anon"; - -grant update on table "public"."user_daily_usage" to "anon"; - -grant delete on table "public"."user_daily_usage" to "authenticated"; - -grant insert on table "public"."user_daily_usage" to "authenticated"; - -grant references on table "public"."user_daily_usage" to "authenticated"; - -grant select on table "public"."user_daily_usage" to "authenticated"; - -grant trigger on table "public"."user_daily_usage" to "authenticated"; - -grant truncate on table "public"."user_daily_usage" to "authenticated"; - -grant update on table "public"."user_daily_usage" to "authenticated"; - -grant delete on table "public"."user_daily_usage" to "service_role"; - -grant insert on table "public"."user_daily_usage" to "service_role"; - -grant references on table "public"."user_daily_usage" to "service_role"; - -grant select on table "public"."user_daily_usage" to "service_role"; - -grant trigger on table "public"."user_daily_usage" to "service_role"; - -grant truncate on table "public"."user_daily_usage" to "service_role"; - -grant update on table "public"."user_daily_usage" to "service_role"; - -grant delete on table "public"."user_identity" to "anon"; - -grant insert on table "public"."user_identity" to "anon"; - -grant references on table "public"."user_identity" to "anon"; - -grant select on table "public"."user_identity" to "anon"; - -grant trigger on table "public"."user_identity" to "anon"; - -grant truncate on table "public"."user_identity" to "anon"; - -grant update on table "public"."user_identity" to "anon"; - -grant delete on table "public"."user_identity" to "authenticated"; - -grant insert on table "public"."user_identity" to "authenticated"; - -grant references on table "public"."user_identity" to "authenticated"; - -grant select on table "public"."user_identity" to "authenticated"; - -grant trigger on table "public"."user_identity" to "authenticated"; - -grant truncate on table "public"."user_identity" to "authenticated"; - -grant update on table "public"."user_identity" to "authenticated"; - -grant delete on table "public"."user_identity" to "service_role"; - -grant insert on table "public"."user_identity" to "service_role"; - -grant references on table "public"."user_identity" to "service_role"; - -grant select on table "public"."user_identity" to "service_role"; - -grant trigger on table "public"."user_identity" to "service_role"; - -grant truncate on table "public"."user_identity" to "service_role"; - -grant update on table "public"."user_identity" to "service_role"; - -grant delete on table "public"."user_settings" to "anon"; - -grant insert on table "public"."user_settings" to "anon"; - -grant references on table "public"."user_settings" to "anon"; - -grant select on table "public"."user_settings" to "anon"; - -grant trigger on table "public"."user_settings" to "anon"; - -grant truncate on table "public"."user_settings" to "anon"; - -grant update on table "public"."user_settings" to "anon"; - -grant delete on table "public"."user_settings" to "authenticated"; - -grant insert on table "public"."user_settings" to "authenticated"; - -grant references on table "public"."user_settings" to "authenticated"; - -grant select on table "public"."user_settings" to "authenticated"; - -grant trigger on table "public"."user_settings" to "authenticated"; - -grant truncate on table "public"."user_settings" to "authenticated"; - -grant update on table "public"."user_settings" to "authenticated"; - -grant delete on table "public"."user_settings" to "service_role"; - -grant insert on table "public"."user_settings" to "service_role"; - -grant references on table "public"."user_settings" to "service_role"; - -grant select on table "public"."user_settings" to "service_role"; - -grant trigger on table "public"."user_settings" to "service_role"; - -grant truncate on table "public"."user_settings" to "service_role"; - -grant update on table "public"."user_settings" to "service_role"; - -grant delete on table "public"."users" to "anon"; - -grant insert on table "public"."users" to "anon"; - -grant references on table "public"."users" to "anon"; - -grant select on table "public"."users" to "anon"; - -grant trigger on table "public"."users" to "anon"; - -grant truncate on table "public"."users" to "anon"; - -grant update on table "public"."users" to "anon"; - -grant delete on table "public"."users" to "authenticated"; - -grant insert on table "public"."users" to "authenticated"; - -grant references on table "public"."users" to "authenticated"; - -grant select on table "public"."users" to "authenticated"; - -grant trigger on table "public"."users" to "authenticated"; - -grant truncate on table "public"."users" to "authenticated"; - -grant update on table "public"."users" to "authenticated"; - -grant delete on table "public"."users" to "service_role"; - -grant insert on table "public"."users" to "service_role"; - -grant references on table "public"."users" to "service_role"; - -grant select on table "public"."users" to "service_role"; - -grant trigger on table "public"."users" to "service_role"; - -grant truncate on table "public"."users" to "service_role"; - -grant update on table "public"."users" to "service_role"; - -grant delete on table "public"."users_old" to "anon"; - -grant insert on table "public"."users_old" to "anon"; - -grant references on table "public"."users_old" to "anon"; - -grant select on table "public"."users_old" to "anon"; - -grant trigger on table "public"."users_old" to "anon"; - -grant truncate on table "public"."users_old" to "anon"; - -grant update on table "public"."users_old" to "anon"; - -grant delete on table "public"."users_old" to "authenticated"; - -grant insert on table "public"."users_old" to "authenticated"; - -grant references on table "public"."users_old" to "authenticated"; - -grant select on table "public"."users_old" to "authenticated"; - -grant trigger on table "public"."users_old" to "authenticated"; - -grant truncate on table "public"."users_old" to "authenticated"; - -grant update on table "public"."users_old" to "authenticated"; - -grant delete on table "public"."users_old" to "service_role"; - -grant insert on table "public"."users_old" to "service_role"; - -grant references on table "public"."users_old" to "service_role"; - -grant select on table "public"."users_old" to "service_role"; - -grant trigger on table "public"."users_old" to "service_role"; - -grant truncate on table "public"."users_old" to "service_role"; - -grant update on table "public"."users_old" to "service_role"; - -grant delete on table "public"."vectors" to "anon"; - -grant insert on table "public"."vectors" to "anon"; - -grant references on table "public"."vectors" to "anon"; - -grant select on table "public"."vectors" to "anon"; - -grant trigger on table "public"."vectors" to "anon"; - -grant truncate on table "public"."vectors" to "anon"; - -grant update on table "public"."vectors" to "anon"; - -grant delete on table "public"."vectors" to "authenticated"; - -grant insert on table "public"."vectors" to "authenticated"; - -grant references on table "public"."vectors" to "authenticated"; - -grant select on table "public"."vectors" to "authenticated"; - -grant trigger on table "public"."vectors" to "authenticated"; - -grant truncate on table "public"."vectors" to "authenticated"; - -grant update on table "public"."vectors" to "authenticated"; - -grant delete on table "public"."vectors" to "service_role"; - -grant insert on table "public"."vectors" to "service_role"; - -grant references on table "public"."vectors" to "service_role"; - -grant select on table "public"."vectors" to "service_role"; - -grant trigger on table "public"."vectors" to "service_role"; - -grant truncate on table "public"."vectors" to "service_role"; - -grant update on table "public"."vectors" to "service_role"; - -grant delete on table "public"."vectors_old" to "anon"; - -grant insert on table "public"."vectors_old" to "anon"; - -grant references on table "public"."vectors_old" to "anon"; - -grant select on table "public"."vectors_old" to "anon"; - -grant trigger on table "public"."vectors_old" to "anon"; - -grant truncate on table "public"."vectors_old" to "anon"; - -grant update on table "public"."vectors_old" to "anon"; - -grant delete on table "public"."vectors_old" to "authenticated"; - -grant insert on table "public"."vectors_old" to "authenticated"; - -grant references on table "public"."vectors_old" to "authenticated"; - -grant select on table "public"."vectors_old" to "authenticated"; - -grant trigger on table "public"."vectors_old" to "authenticated"; - -grant truncate on table "public"."vectors_old" to "authenticated"; - -grant update on table "public"."vectors_old" to "authenticated"; - -grant delete on table "public"."vectors_old" to "service_role"; - -grant insert on table "public"."vectors_old" to "service_role"; - -grant references on table "public"."vectors_old" to "service_role"; - -grant select on table "public"."vectors_old" to "service_role"; - -grant trigger on table "public"."vectors_old" to "service_role"; - -grant truncate on table "public"."vectors_old" to "service_role"; - -grant update on table "public"."vectors_old" to "service_role"; - - -create schema if not exists "stripe"; - - --- Create users table -CREATE TABLE IF NOT EXISTS user_daily_usage( - user_id UUID REFERENCES auth.users (id), - email TEXT, - date TEXT, - daily_requests_count INT, - PRIMARY KEY (user_id, date) -); - --- Create chats table -CREATE TABLE IF NOT EXISTS chats( - chat_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - user_id UUID REFERENCES auth.users (id), - creation_time TIMESTAMP DEFAULT current_timestamp, - history JSONB, - chat_name TEXT -); - - --- Create vector extension -CREATE EXTENSION IF NOT EXISTS vector; - --- Create vectors table -CREATE TABLE IF NOT EXISTS vectors ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - content TEXT, - file_sha1 TEXT, - metadata JSONB, - embedding VECTOR(1536) -); - --- Create function to match vectors -CREATE OR REPLACE FUNCTION match_vectors(query_embedding VECTOR(1536), match_count INT, p_brain_id UUID) -RETURNS TABLE( - id UUID, - brain_id UUID, - content TEXT, - metadata JSONB, - embedding VECTOR(1536), - similarity FLOAT -) LANGUAGE plpgsql AS $$ -#variable_conflict use_column -BEGIN - RETURN QUERY - SELECT - vectors.id, - brains_vectors.brain_id, - vectors.content, - vectors.metadata, - vectors.embedding, - 1 - (vectors.embedding <=> query_embedding) AS similarity - FROM - vectors - INNER JOIN - brains_vectors ON vectors.id = brains_vectors.vector_id - WHERE brains_vectors.brain_id = p_brain_id - ORDER BY - vectors.embedding <=> query_embedding - LIMIT match_count; -END; -$$; - --- Create stats table -CREATE TABLE IF NOT EXISTS stats ( - time TIMESTAMP, - chat BOOLEAN, - embedding BOOLEAN, - details TEXT, - metadata JSONB, - id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY -); - --- Create summaries table -CREATE TABLE IF NOT EXISTS summaries ( - id BIGSERIAL PRIMARY KEY, - document_id UUID REFERENCES vectors(id), - content TEXT, - metadata JSONB, - embedding VECTOR(1536) -); - --- Create function to match summaries -CREATE OR REPLACE FUNCTION match_summaries(query_embedding VECTOR(1536), match_count INT, match_threshold FLOAT) -RETURNS TABLE( - id BIGINT, - document_id UUID, - content TEXT, - metadata JSONB, - embedding VECTOR(1536), - similarity FLOAT -) LANGUAGE plpgsql AS $$ -#variable_conflict use_column -BEGIN - RETURN QUERY - SELECT - id, - document_id, - content, - metadata, - embedding, - 1 - (summaries.embedding <=> query_embedding) AS similarity - FROM - summaries - WHERE 1 - (summaries.embedding <=> query_embedding) > match_threshold - ORDER BY - summaries.embedding <=> query_embedding - LIMIT match_count; -END; -$$; - --- Create api_keys table -CREATE TABLE IF NOT EXISTS api_keys( - key_id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID REFERENCES auth.users (id), - name TEXT DEFAULT 'API_KEY', - days INT DEFAULT 30, - only_chat BOOLEAN DEFAULT false, - api_key TEXT UNIQUE, - creation_time TIMESTAMP DEFAULT current_timestamp, - deleted_time TIMESTAMP, - is_active BOOLEAN DEFAULT true -); - ---- Create prompts table -CREATE TABLE IF NOT EXISTS prompts ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - title VARCHAR(255), - content TEXT, - status VARCHAR(255) DEFAULT 'private' -); - -DO $$ -BEGIN -IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'brain_type_enum') THEN - -- Create the ENUM type 'brain_type' if it doesn't exist - CREATE TYPE brain_type_enum AS ENUM ('doc', 'api', 'composite'); -END IF; -END $$; - ---- Create brains table -CREATE TABLE IF NOT EXISTS brains ( - brain_id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - name TEXT NOT NULL, - status TEXT, - description TEXT, - model TEXT, - max_tokens INT, - temperature FLOAT, - prompt_id UUID REFERENCES prompts(id), - last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - brain_type brain_type_enum DEFAULT 'doc' -); - - --- Create chat_history table -CREATE TABLE IF NOT EXISTS chat_history ( - message_id UUID DEFAULT uuid_generate_v4(), - chat_id UUID REFERENCES chats(chat_id), - user_message TEXT, - assistant TEXT, - message_time TIMESTAMP DEFAULT current_timestamp, - PRIMARY KEY (chat_id, message_id), - prompt_id UUID REFERENCES prompts(id), - brain_id UUID REFERENCES brains(brain_id) -); - --- Create notification table - -CREATE TABLE IF NOT EXISTS notifications ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - chat_id UUID REFERENCES chats(chat_id), - message TEXT, - action VARCHAR(255) NOT NULL, - status VARCHAR(255) NOT NULL -); - - --- Create brains X users table -CREATE TABLE IF NOT EXISTS brains_users ( - brain_id UUID, - user_id UUID, - rights VARCHAR(255), - default_brain BOOLEAN DEFAULT false, - PRIMARY KEY (brain_id, user_id), - FOREIGN KEY (user_id) REFERENCES auth.users (id), - FOREIGN KEY (brain_id) REFERENCES brains (brain_id) -); - --- Create brains X vectors table -CREATE TABLE IF NOT EXISTS brains_vectors ( - brain_id UUID, - vector_id UUID, - file_sha1 TEXT, - PRIMARY KEY (brain_id, vector_id), - FOREIGN KEY (vector_id) REFERENCES vectors (id), - FOREIGN KEY (brain_id) REFERENCES brains (brain_id) -); - --- Create brains X vectors table -CREATE TABLE IF NOT EXISTS brain_subscription_invitations ( - brain_id UUID, - email VARCHAR(255), - rights VARCHAR(255), - PRIMARY KEY (brain_id, email), - FOREIGN KEY (brain_id) REFERENCES brains (brain_id) -); - --- Table for storing the relationship between brains for composite brains -CREATE TABLE IF NOT EXISTS composite_brain_connections ( - composite_brain_id UUID NOT NULL REFERENCES brains(brain_id), - connected_brain_id UUID NOT NULL REFERENCES brains(brain_id), - PRIMARY KEY (composite_brain_id, connected_brain_id), - CHECK (composite_brain_id != connected_brain_id) -); - ---- Create user_identity table -CREATE TABLE IF NOT EXISTS user_identity ( - user_id UUID PRIMARY KEY, - openai_api_key VARCHAR(255) -); - --- Create the new table with 6 columns -CREATE TABLE IF NOT EXISTS api_brain_definition ( - brain_id UUID REFERENCES brains(brain_id), - method VARCHAR(255) CHECK (method IN ('GET', 'POST', 'PUT', 'DELETE')), - url VARCHAR(255), - params JSON, - search_params JSON, - secrets JSON -); - -CREATE OR REPLACE FUNCTION public.get_user_email_by_user_id(user_id uuid) -RETURNS TABLE (email text) -SECURITY definer -AS $$ -BEGIN - RETURN QUERY SELECT au.email::text FROM auth.users au WHERE au.id = user_id; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION public.get_user_id_by_user_email(user_email text) -RETURNS TABLE (user_id uuid) -SECURITY DEFINER -AS $$ -BEGIN - RETURN QUERY SELECT au.id::uuid FROM auth.users au WHERE au.email = user_email; -END; -$$ LANGUAGE plpgsql; - - - - - -CREATE TABLE IF NOT EXISTS user_settings ( - user_id UUID PRIMARY KEY, - models JSONB DEFAULT '["gpt-3.5-turbo-1106","gpt-4"]'::jsonb, - daily_chat_credit INT DEFAULT 300, - max_brains INT DEFAULT 30, - max_brain_size INT DEFAULT 100000000 -); - --- knowledge table -CREATE TABLE IF NOT EXISTS knowledge ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - file_name TEXT, - url TEXT, - brain_id UUID NOT NULL REFERENCES brains(brain_id), - extension TEXT NOT NULL, - CHECK ((file_name IS NOT NULL AND url IS NULL) OR (file_name IS NULL AND url IS NOT NULL)) -); - - --- knowledge_vectors table -CREATE TABLE IF NOT EXISTS knowledge_vectors ( - knowledge_id UUID NOT NULL REFERENCES knowledge(id), - vector_id UUID NOT NULL REFERENCES vectors(id), - embedding_model TEXT NOT NULL, - PRIMARY KEY (knowledge_id, vector_id, embedding_model) -); - --- Create the function to add user_id to the onboardings table -CREATE OR REPLACE FUNCTION public.create_user_onboarding() RETURNS TRIGGER AS $$ -BEGIN - INSERT INTO public.onboardings (user_id) - VALUES (NEW.id); - RETURN NEW; -END; -$$ LANGUAGE plpgsql SECURITY definer; - --- Revoke all on function handle_new_user_onboarding() from PUBLIC; -REVOKE ALL ON FUNCTION create_user_onboarding() FROM PUBLIC; - --- Drop the trigger if it exists -DROP TRIGGER IF EXISTS create_user_onboarding_trigger ON auth.users; - --- Create the trigger on the insert into the auth.users table -CREATE TRIGGER create_user_onboarding_trigger -AFTER INSERT ON auth.users -FOR EACH ROW -EXECUTE FUNCTION public.create_user_onboarding(); - --- Create the onboarding table -CREATE TABLE IF NOT EXISTS onboardings ( - user_id UUID NOT NULL REFERENCES auth.users (id), - onboarding_a BOOLEAN NOT NULL DEFAULT true, - onboarding_b1 BOOLEAN NOT NULL DEFAULT true, - onboarding_b2 BOOLEAN NOT NULL DEFAULT true, - onboarding_b3 BOOLEAN NOT NULL DEFAULT true, - creation_time TIMESTAMP DEFAULT current_timestamp, - PRIMARY KEY (user_id) -); - - --- Stripe settings -- --- Create extension 'wrappers' if it doesn't exist -CREATE EXTENSION IF NOT EXISTS wrappers; - --- Create foreign data wrapper 'stripe_wrapper' if it doesn't exist -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.foreign_data_wrappers - WHERE foreign_data_wrapper_name = 'stripe_wrapper' - ) THEN - CREATE FOREIGN DATA WRAPPER stripe_wrapper - HANDLER stripe_fdw_handler; - END IF; -END $$; - --- Check if the server 'stripe_server' exists before creating it -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname = 'stripe_server') THEN - CREATE SERVER stripe_server - FOREIGN DATA WRAPPER stripe_wrapper - OPTIONS ( - api_key 'sk_test_51NtDTIJglvQxkJ1HVZHZHpKNAm48jAzKfJs93MjpKiML9YHy8G1YoKIf6SpcnGwRFWjmdS664A2Z2dn4LORWpo1P00qt6Jmy8G' -- Replace with your Stripe API key - ); - END IF; -END $$; - --- Create foreign table 'public.customers' if it doesn't exist -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.tables - WHERE table_name = 'customers' - ) THEN - CREATE FOREIGN TABLE public.customers ( - id text, - email text, - name text, - description text, - created timestamp, - attrs jsonb - ) - SERVER stripe_server - OPTIONS ( - OBJECT 'customers', - ROWID_COLUMN 'id' - ); - END IF; -END $$; - --- Create table 'users' if it doesn't exist -CREATE TABLE IF NOT EXISTS public.users ( - id uuid REFERENCES auth.users NOT NULL PRIMARY KEY, - email text -); - --- Create or replace function 'public.handle_new_user' -CREATE OR REPLACE FUNCTION public.handle_new_user() -RETURNS TRIGGER AS $$ -BEGIN - INSERT INTO public.users (id, email) - VALUES (NEW.id, NEW.email); - RETURN NEW; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- Check if the trigger 'on_auth_user_created' exists before creating it -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'on_auth_user_created') THEN - CREATE TRIGGER on_auth_user_created - AFTER INSERT ON auth.users - FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); - END IF; -END $$; - -insert into - storage.buckets (id, name) -values - ('quivr', 'quivr'); - -CREATE POLICY "Access Quivr Storage 1jccrwz_0" ON storage.objects FOR INSERT TO anon WITH CHECK (bucket_id = 'quivr'); - -CREATE POLICY "Access Quivr Storage 1jccrwz_1" ON storage.objects FOR SELECT TO anon USING (bucket_id = 'quivr'); - -CREATE POLICY "Access Quivr Storage 1jccrwz_2" ON storage.objects FOR UPDATE TO anon USING (bucket_id = 'quivr'); - -CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr'); - --- Create functions for secrets in vault -CREATE OR REPLACE FUNCTION insert_secret(name text, secret text) -returns uuid -language plpgsql -security definer -set search_path = public -as $$ -begin - return vault.create_secret(secret, name); -end; -$$; - - -create or replace function read_secret(secret_name text) -returns text -language plpgsql -security definer set search_path = public -as $$ -declare - secret text; -begin - select decrypted_secret from vault.decrypted_secrets where name = - secret_name into secret; - return secret; -end; -$$; - -create or replace function delete_secret(secret_name text) -returns text -language plpgsql -security definer set search_path = public -as $$ -declare - deleted_rows int; -begin - delete from vault.decrypted_secrets where name = secret_name; - get diagnostics deleted_rows = row_count; - if deleted_rows = 0 then - return false; - else - return true; - end if; -end; -$$; - -create schema if not exists extensions; - -create table if not exists - extensions.wrappers_fdw_stats (); - -grant all on extensions.wrappers_fdw_stats to service_role; diff --git a/backend/supabase/migrations/20240103175048_prod.sql b/backend/supabase/migrations/20240103175048_prod.sql deleted file mode 100644 index c1b4efda9..000000000 --- a/backend/supabase/migrations/20240103175048_prod.sql +++ /dev/null @@ -1,203 +0,0 @@ -create sequence "public"."documents_id_seq"; - -drop function if exists "public"."get_premium_user"(input_email text); - -drop function if exists "public"."update_max_brains"(); - -drop function if exists "public"."update_user_settings"(); - -drop function if exists "public"."match_summaries"(query_embedding vector, match_count integer, match_threshold double precision); - -alter table "public"."vectors" drop constraint "vectors_pkey1"; - -drop index if exists "public"."vectors_pkey1"; - -create table "public"."documents" ( - "id" bigint not null default nextval('documents_id_seq'::regclass), - "content" text, - "metadata" jsonb, - "embedding" vector(1536) -); - - -alter table "public"."brains" drop column "retrieval_algorithm"; - -alter table "public"."brains" add column "openai_api_key" text; - -alter table "public"."brains" alter column "status" set default 'private'::text; - -alter table "public"."brains_users" alter column "default_brain" set default false; - -alter table "public"."brains_vectors" drop column "rights"; - -alter table "public"."user_settings" alter column "max_brain_size" set default 50000000; - -alter table "public"."vectors" alter column "id" drop default; - -alter sequence "public"."documents_id_seq" owned by "public"."documents"."id"; - -CREATE INDEX brains_vectors_brain_id_idx ON public.brains_vectors USING btree (brain_id); - -CREATE INDEX brains_vectors_vector_id_idx ON public.brains_vectors USING btree (vector_id); - -CREATE UNIQUE INDEX documents_pkey ON public.documents USING btree (id); - -CREATE INDEX idx_brains_vectors_vector_id ON public.brains_vectors USING btree (vector_id); - -CREATE INDEX idx_vectors_id ON public.vectors USING btree (id); - -CREATE INDEX vectors_file_sha1_idx ON public.vectors USING btree (file_sha1); - -CREATE INDEX vectors_id_idx ON public.vectors USING btree (id); - -CREATE UNIQUE INDEX vectors_new_pkey ON public.vectors USING btree (id); - -alter table "public"."documents" add constraint "documents_pkey" PRIMARY KEY using index "documents_pkey"; - -alter table "public"."vectors" add constraint "vectors_new_pkey" PRIMARY KEY using index "vectors_new_pkey"; - -alter table "public"."api_keys" add constraint "api_keys_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid; - -alter table "public"."api_keys" validate constraint "api_keys_user_id_fkey"; - -alter table "public"."brains_vectors" add constraint "brains_vectors_vector_id_fkey" FOREIGN KEY (vector_id) REFERENCES vectors(id) not valid; - -alter table "public"."brains_vectors" validate constraint "brains_vectors_vector_id_fkey"; - -alter table "public"."knowledge_vectors" add constraint "knowledge_vectors_vector_id_fkey" FOREIGN KEY (vector_id) REFERENCES vectors(id) not valid; - -alter table "public"."knowledge_vectors" validate constraint "knowledge_vectors_vector_id_fkey"; - -alter table "public"."summaries" add constraint "summaries_document_id_fkey" FOREIGN KEY (document_id) REFERENCES vectors(id) not valid; - -alter table "public"."summaries" validate constraint "summaries_document_id_fkey"; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.match_documents(query_embedding vector, match_count integer) - RETURNS TABLE(id bigint, content text, metadata jsonb, similarity double precision) - LANGUAGE plpgsql -AS $function$ -#variable_conflict use_column -begin - return query - select - id, - content, - metadata, - 1 - (documents.embedding <=> query_embedding) as similarity - from documents - order by documents.embedding <=> query_embedding - limit match_count; -end; -$function$ -; - -CREATE OR REPLACE FUNCTION public.match_summaries(query_embedding vector, match_count integer, match_threshold double precision) - RETURNS TABLE(id bigint, document_id bigint, content text, metadata jsonb, embedding vector, similarity double precision) - LANGUAGE plpgsql -AS $function$ - # variable_conflict use_column -BEGIN - RETURN query - SELECT - id, - document_id, - content, - metadata, - embedding, - 1 -(summaries.embedding <=> query_embedding) AS similarity - FROM - summaries - WHERE 1 - (summaries.embedding <=> query_embedding) > match_threshold - ORDER BY - summaries.embedding <=> query_embedding - LIMIT match_count; -END; -$function$ -; - -CREATE OR REPLACE FUNCTION public.update_max_brains_theodo() - RETURNS trigger - LANGUAGE plpgsql - SECURITY DEFINER -AS $function$ -DECLARE - userEmail TEXT; - allowedDomains TEXT[] := ARRAY['%@theodo.fr', '%@theodo.com', '%@theodo.co.uk', '%@bam.tech', '%@padok.fr', '%@aleios.com', '%@sicara.com', '%@hokla.com', '%@sipios.com']; -BEGIN - SELECT email INTO userEmail FROM auth.users WHERE id = NEW.user_id; - - IF userEmail LIKE ANY(allowedDomains) THEN - -- Ensure the models column is initialized as an array if null - IF NEW.models IS NULL THEN - NEW.models := '[]'::jsonb; - END IF; - - -- Add gpt-4 if not present - IF NOT NEW.models ? 'gpt-4' THEN - NEW.models := NEW.models || '["gpt-4"]'::jsonb; - END IF; - - -- Add gpt-3.5-turbo if not present - IF NOT NEW.models ? 'gpt-3.5-turbo-1106' THEN - NEW.models := NEW.models || '["gpt-3.5-turbo"]'::jsonb; - END IF; - - UPDATE user_settings - SET - max_brains = 30, - max_brain_size = 100000000, - daily_chat_credit = 200, - models = NEW.models - WHERE user_id = NEW.user_id; - END IF; - - RETURN NULL; -- for AFTER triggers, the return value is ignored -END; -$function$ -; - -grant delete on table "public"."documents" to "anon"; - -grant insert on table "public"."documents" to "anon"; - -grant references on table "public"."documents" to "anon"; - -grant select on table "public"."documents" to "anon"; - -grant trigger on table "public"."documents" to "anon"; - -grant truncate on table "public"."documents" to "anon"; - -grant update on table "public"."documents" to "anon"; - -grant delete on table "public"."documents" to "authenticated"; - -grant insert on table "public"."documents" to "authenticated"; - -grant references on table "public"."documents" to "authenticated"; - -grant select on table "public"."documents" to "authenticated"; - -grant trigger on table "public"."documents" to "authenticated"; - -grant truncate on table "public"."documents" to "authenticated"; - -grant update on table "public"."documents" to "authenticated"; - -grant delete on table "public"."documents" to "service_role"; - -grant insert on table "public"."documents" to "service_role"; - -grant references on table "public"."documents" to "service_role"; - -grant select on table "public"."documents" to "service_role"; - -grant trigger on table "public"."documents" to "service_role"; - -grant truncate on table "public"."documents" to "service_role"; - -grant update on table "public"."documents" to "service_role"; - -CREATE TRIGGER update_max_brains_theodo_trigger AFTER INSERT ON public.user_settings FOR EACH ROW EXECUTE FUNCTION update_max_brains_theodo(); diff --git a/backend/supabase/migrations/20240103181249_premium.sql b/backend/supabase/migrations/20240103181249_premium.sql deleted file mode 100644 index 57f3edb1c..000000000 --- a/backend/supabase/migrations/20240103181249_premium.sql +++ /dev/null @@ -1,5 +0,0 @@ -alter table "public"."user_settings" add column "is_premium" boolean not null default false; - -alter table "public"."user_settings" alter column "max_brain_size" set not null; - -alter table "public"."user_settings" alter column "max_brain_size" set data type bigint using "max_brain_size"::bigint; diff --git a/backend/supabase/migrations/20240103181925_cleanup.sql b/backend/supabase/migrations/20240103181925_cleanup.sql deleted file mode 100644 index 7f8b2c122..000000000 --- a/backend/supabase/migrations/20240103181925_cleanup.sql +++ /dev/null @@ -1 +0,0 @@ -drop function if exists "public"."match_summaries"(query_embedding vector, match_count integer, match_threshold double precision); diff --git a/backend/supabase/migrations/20240103193921_stripe_customers.sql b/backend/supabase/migrations/20240103193921_stripe_customers.sql deleted file mode 100644 index 9d8b04c2a..000000000 --- a/backend/supabase/migrations/20240103193921_stripe_customers.sql +++ /dev/null @@ -1,30 +0,0 @@ -create foreign table public.subscriptions ( - id text, - customer text, - currency text, - current_period_start timestamp, - current_period_end timestamp, - attrs jsonb -) - server stripe_server - options ( - object 'subscriptions', - rowid_column 'id' - ); - - - create foreign table public.products ( - id text, - name text, - active bool, - default_price text, - description text, - created timestamp, - updated timestamp, - attrs jsonb -) - server stripe_server - options ( - object 'products', - rowid_column 'id' - ); diff --git a/backend/supabase/migrations/20240103194255_api.sql b/backend/supabase/migrations/20240103194255_api.sql deleted file mode 100644 index ae741de5a..000000000 --- a/backend/supabase/migrations/20240103194255_api.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."user_settings" add column "API_ACCESS" boolean not null default false; diff --git a/backend/supabase/migrations/20240103204741_product_to_features.sql b/backend/supabase/migrations/20240103204741_product_to_features.sql deleted file mode 100644 index 739e1bf0f..000000000 --- a/backend/supabase/migrations/20240103204741_product_to_features.sql +++ /dev/null @@ -1,64 +0,0 @@ -create table "public"."product_to_features" ( - "id" bigint generated by default as identity not null, - "models" jsonb default '["gpt-3.5-turbo-1106"]'::jsonb, - "daily_chat_credit" integer not null default 20, - "max_brains" integer not null, - "max_brain_size" bigint not null default '50000000'::bigint, - "api_access" boolean not null default false, - "stripe_product_id" text -); - - -alter table "public"."user_settings" drop column "API_ACCESS"; - -alter table "public"."user_settings" add column "api_access" boolean not null default false; - -CREATE UNIQUE INDEX product_to_features_pkey ON public.product_to_features USING btree (id); - -alter table "public"."product_to_features" add constraint "product_to_features_pkey" PRIMARY KEY using index "product_to_features_pkey"; - -alter table "public"."product_to_features" add constraint "product_to_features_max_brains_check" CHECK ((max_brains > 0)) not valid; - -alter table "public"."product_to_features" validate constraint "product_to_features_max_brains_check"; - -grant delete on table "public"."product_to_features" to "anon"; - -grant insert on table "public"."product_to_features" to "anon"; - -grant references on table "public"."product_to_features" to "anon"; - -grant select on table "public"."product_to_features" to "anon"; - -grant trigger on table "public"."product_to_features" to "anon"; - -grant truncate on table "public"."product_to_features" to "anon"; - -grant update on table "public"."product_to_features" to "anon"; - -grant delete on table "public"."product_to_features" to "authenticated"; - -grant insert on table "public"."product_to_features" to "authenticated"; - -grant references on table "public"."product_to_features" to "authenticated"; - -grant select on table "public"."product_to_features" to "authenticated"; - -grant trigger on table "public"."product_to_features" to "authenticated"; - -grant truncate on table "public"."product_to_features" to "authenticated"; - -grant update on table "public"."product_to_features" to "authenticated"; - -grant delete on table "public"."product_to_features" to "service_role"; - -grant insert on table "public"."product_to_features" to "service_role"; - -grant references on table "public"."product_to_features" to "service_role"; - -grant select on table "public"."product_to_features" to "service_role"; - -grant trigger on table "public"."product_to_features" to "service_role"; - -grant truncate on table "public"."product_to_features" to "service_role"; - -grant update on table "public"."product_to_features" to "service_role"; diff --git a/backend/supabase/migrations/20240103231656_product.sql b/backend/supabase/migrations/20240103231656_product.sql deleted file mode 100644 index 796c8459d..000000000 --- a/backend/supabase/migrations/20240103231656_product.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."product_to_features" alter column "models" set not null; diff --git a/backend/supabase/migrations/20240103234423_models.sql b/backend/supabase/migrations/20240103234423_models.sql deleted file mode 100644 index 3b53727ff..000000000 --- a/backend/supabase/migrations/20240103234423_models.sql +++ /dev/null @@ -1,53 +0,0 @@ -create table "public"."models" ( - "name" text not null, - "price" integer default 1, - "max_input" integer default 2000, - "max_output" integer default 1000 -); - - -CREATE UNIQUE INDEX models_pkey ON public.models USING btree (name); - -alter table "public"."models" add constraint "models_pkey" PRIMARY KEY using index "models_pkey"; - -grant delete on table "public"."models" to "anon"; - -grant insert on table "public"."models" to "anon"; - -grant references on table "public"."models" to "anon"; - -grant select on table "public"."models" to "anon"; - -grant trigger on table "public"."models" to "anon"; - -grant truncate on table "public"."models" to "anon"; - -grant update on table "public"."models" to "anon"; - -grant delete on table "public"."models" to "authenticated"; - -grant insert on table "public"."models" to "authenticated"; - -grant references on table "public"."models" to "authenticated"; - -grant select on table "public"."models" to "authenticated"; - -grant trigger on table "public"."models" to "authenticated"; - -grant truncate on table "public"."models" to "authenticated"; - -grant update on table "public"."models" to "authenticated"; - -grant delete on table "public"."models" to "service_role"; - -grant insert on table "public"."models" to "service_role"; - -grant references on table "public"."models" to "service_role"; - -grant select on table "public"."models" to "service_role"; - -grant trigger on table "public"."models" to "service_role"; - -grant truncate on table "public"."models" to "service_role"; - -grant update on table "public"."models" to "service_role"; diff --git a/backend/supabase/migrations/20240107231636_policies.sql b/backend/supabase/migrations/20240107231636_policies.sql deleted file mode 100644 index 72516fe5a..000000000 --- a/backend/supabase/migrations/20240107231636_policies.sql +++ /dev/null @@ -1,486 +0,0 @@ -revoke delete on table "public"."knowledge_vectors" from "anon"; - -revoke insert on table "public"."knowledge_vectors" from "anon"; - -revoke references on table "public"."knowledge_vectors" from "anon"; - -revoke select on table "public"."knowledge_vectors" from "anon"; - -revoke trigger on table "public"."knowledge_vectors" from "anon"; - -revoke truncate on table "public"."knowledge_vectors" from "anon"; - -revoke update on table "public"."knowledge_vectors" from "anon"; - -revoke delete on table "public"."knowledge_vectors" from "authenticated"; - -revoke insert on table "public"."knowledge_vectors" from "authenticated"; - -revoke references on table "public"."knowledge_vectors" from "authenticated"; - -revoke select on table "public"."knowledge_vectors" from "authenticated"; - -revoke trigger on table "public"."knowledge_vectors" from "authenticated"; - -revoke truncate on table "public"."knowledge_vectors" from "authenticated"; - -revoke update on table "public"."knowledge_vectors" from "authenticated"; - -revoke delete on table "public"."knowledge_vectors" from "service_role"; - -revoke insert on table "public"."knowledge_vectors" from "service_role"; - -revoke references on table "public"."knowledge_vectors" from "service_role"; - -revoke select on table "public"."knowledge_vectors" from "service_role"; - -revoke trigger on table "public"."knowledge_vectors" from "service_role"; - -revoke truncate on table "public"."knowledge_vectors" from "service_role"; - -revoke update on table "public"."knowledge_vectors" from "service_role"; - -revoke delete on table "public"."stats" from "anon"; - -revoke insert on table "public"."stats" from "anon"; - -revoke references on table "public"."stats" from "anon"; - -revoke select on table "public"."stats" from "anon"; - -revoke trigger on table "public"."stats" from "anon"; - -revoke truncate on table "public"."stats" from "anon"; - -revoke update on table "public"."stats" from "anon"; - -revoke delete on table "public"."stats" from "authenticated"; - -revoke insert on table "public"."stats" from "authenticated"; - -revoke references on table "public"."stats" from "authenticated"; - -revoke select on table "public"."stats" from "authenticated"; - -revoke trigger on table "public"."stats" from "authenticated"; - -revoke truncate on table "public"."stats" from "authenticated"; - -revoke update on table "public"."stats" from "authenticated"; - -revoke delete on table "public"."stats" from "service_role"; - -revoke insert on table "public"."stats" from "service_role"; - -revoke references on table "public"."stats" from "service_role"; - -revoke select on table "public"."stats" from "service_role"; - -revoke trigger on table "public"."stats" from "service_role"; - -revoke truncate on table "public"."stats" from "service_role"; - -revoke update on table "public"."stats" from "service_role"; - -revoke delete on table "public"."summaries" from "anon"; - -revoke insert on table "public"."summaries" from "anon"; - -revoke references on table "public"."summaries" from "anon"; - -revoke select on table "public"."summaries" from "anon"; - -revoke trigger on table "public"."summaries" from "anon"; - -revoke truncate on table "public"."summaries" from "anon"; - -revoke update on table "public"."summaries" from "anon"; - -revoke delete on table "public"."summaries" from "authenticated"; - -revoke insert on table "public"."summaries" from "authenticated"; - -revoke references on table "public"."summaries" from "authenticated"; - -revoke select on table "public"."summaries" from "authenticated"; - -revoke trigger on table "public"."summaries" from "authenticated"; - -revoke truncate on table "public"."summaries" from "authenticated"; - -revoke update on table "public"."summaries" from "authenticated"; - -revoke delete on table "public"."summaries" from "service_role"; - -revoke insert on table "public"."summaries" from "service_role"; - -revoke references on table "public"."summaries" from "service_role"; - -revoke select on table "public"."summaries" from "service_role"; - -revoke trigger on table "public"."summaries" from "service_role"; - -revoke truncate on table "public"."summaries" from "service_role"; - -revoke update on table "public"."summaries" from "service_role"; - -revoke delete on table "public"."users_old" from "anon"; - -revoke insert on table "public"."users_old" from "anon"; - -revoke references on table "public"."users_old" from "anon"; - -revoke select on table "public"."users_old" from "anon"; - -revoke trigger on table "public"."users_old" from "anon"; - -revoke truncate on table "public"."users_old" from "anon"; - -revoke update on table "public"."users_old" from "anon"; - -revoke delete on table "public"."users_old" from "authenticated"; - -revoke insert on table "public"."users_old" from "authenticated"; - -revoke references on table "public"."users_old" from "authenticated"; - -revoke select on table "public"."users_old" from "authenticated"; - -revoke trigger on table "public"."users_old" from "authenticated"; - -revoke truncate on table "public"."users_old" from "authenticated"; - -revoke update on table "public"."users_old" from "authenticated"; - -revoke delete on table "public"."users_old" from "service_role"; - -revoke insert on table "public"."users_old" from "service_role"; - -revoke references on table "public"."users_old" from "service_role"; - -revoke select on table "public"."users_old" from "service_role"; - -revoke trigger on table "public"."users_old" from "service_role"; - -revoke truncate on table "public"."users_old" from "service_role"; - -revoke update on table "public"."users_old" from "service_role"; - -revoke delete on table "public"."vectors_old" from "anon"; - -revoke insert on table "public"."vectors_old" from "anon"; - -revoke references on table "public"."vectors_old" from "anon"; - -revoke select on table "public"."vectors_old" from "anon"; - -revoke trigger on table "public"."vectors_old" from "anon"; - -revoke truncate on table "public"."vectors_old" from "anon"; - -revoke update on table "public"."vectors_old" from "anon"; - -revoke delete on table "public"."vectors_old" from "authenticated"; - -revoke insert on table "public"."vectors_old" from "authenticated"; - -revoke references on table "public"."vectors_old" from "authenticated"; - -revoke select on table "public"."vectors_old" from "authenticated"; - -revoke trigger on table "public"."vectors_old" from "authenticated"; - -revoke truncate on table "public"."vectors_old" from "authenticated"; - -revoke update on table "public"."vectors_old" from "authenticated"; - -revoke delete on table "public"."vectors_old" from "service_role"; - -revoke insert on table "public"."vectors_old" from "service_role"; - -revoke references on table "public"."vectors_old" from "service_role"; - -revoke select on table "public"."vectors_old" from "service_role"; - -revoke trigger on table "public"."vectors_old" from "service_role"; - -revoke truncate on table "public"."vectors_old" from "service_role"; - -revoke update on table "public"."vectors_old" from "service_role"; - -alter table "public"."knowledge_vectors" drop constraint "knowledge_vectors_knowledge_id_fkey"; - -alter table "public"."notifications" drop constraint "notifications_chat_id_fkey"; - -alter table "public"."api_keys" drop constraint "api_keys_user_id_fkey"; - -alter table "public"."brains_users" drop constraint "brains_users_brain_id_fkey"; - -alter table "public"."brains_users" drop constraint "brains_users_user_id_fkey"; - -alter table "public"."chat_history" drop constraint "chat_history_chat_id_fkey"; - -alter table "public"."chats" drop constraint "chats_user_id_fkey"; - -alter table "public"."onboardings" drop constraint "onboardings_user_id_fkey"; - -alter table "public"."user_daily_usage" drop constraint "user_daily_usage_user_id_fkey"; - -alter table "public"."users" drop constraint "users_id_fkey"; - -alter table "public"."knowledge_vectors" drop constraint "knowledge_vectors_pkey"; - -alter table "public"."stats" drop constraint "stats_pkey"; - -alter table "public"."summaries" drop constraint "summaries_pkey"; - -alter table "public"."vectors_old" drop constraint "vectors_pkey"; - -drop table if exists "public"."documents"; - -drop table "public"."knowledge_vectors"; - -drop table if exists "public"."migrations"; - -drop table "public"."stats"; - -drop table "public"."summaries"; - -drop table "public"."users_old"; - -drop table "public"."vectors_old"; - -drop index if exists "public"."knowledge_vectors_pkey"; - -drop index if exists "public"."migrations_pkey"; - -drop index if exists "public"."stats_pkey"; - -drop index if exists "public"."summaries_pkey"; - -drop index if exists "public"."vectors_pkey"; - -alter table "public"."api_brain_definition" enable row level security; - -alter table "public"."api_keys" enable row level security; - -alter table "public"."brain_subscription_invitations" enable row level security; - -alter table "public"."brains" enable row level security; - -alter table "public"."brains_users" enable row level security; - -alter table "public"."brains_vectors" enable row level security; - -alter table "public"."chat_history" enable row level security; - -alter table "public"."chats" enable row level security; - -alter table "public"."composite_brain_connections" enable row level security; - -alter table "public"."knowledge" enable row level security; - -alter table "public"."models" enable row level security; - -alter table "public"."notifications" enable row level security; - -alter table "public"."onboardings" enable row level security; - -alter table "public"."product_to_features" enable row level security; - -alter table "public"."prompts" enable row level security; - -alter table "public"."user_daily_usage" enable row level security; - -alter table "public"."user_identity" enable row level security; - -alter table "public"."user_settings" enable row level security; - -alter table "public"."users" enable row level security; - -alter table "public"."vectors" enable row level security; - -drop sequence if exists "public"."documents_id_seq"; - -drop sequence if exists "public"."summaries_id_seq"; - -drop sequence if exists "public"."vectors_id_seq"; - -alter table "public"."user_identity" add constraint "user_identity_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."user_identity" validate constraint "user_identity_user_id_fkey"; - -alter table "public"."user_settings" add constraint "user_settings_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."user_settings" validate constraint "user_settings_user_id_fkey"; - -alter table "public"."api_keys" add constraint "api_keys_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."api_keys" validate constraint "api_keys_user_id_fkey"; - -alter table "public"."brains_users" add constraint "brains_users_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON DELETE CASCADE not valid; - -alter table "public"."brains_users" validate constraint "brains_users_brain_id_fkey"; - -alter table "public"."brains_users" add constraint "brains_users_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."brains_users" validate constraint "brains_users_user_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_chat_id_fkey" FOREIGN KEY (chat_id) REFERENCES chats(chat_id) ON DELETE CASCADE not valid; - -alter table "public"."chat_history" validate constraint "chat_history_chat_id_fkey"; - -alter table "public"."chats" add constraint "chats_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."chats" validate constraint "chats_user_id_fkey"; - -alter table "public"."onboardings" add constraint "onboardings_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."onboardings" validate constraint "onboardings_user_id_fkey"; - -alter table "public"."user_daily_usage" add constraint "user_daily_usage_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."user_daily_usage" validate constraint "user_daily_usage_user_id_fkey"; - -alter table "public"."users" add constraint "users_id_fkey" FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE not valid; - -alter table "public"."users" validate constraint "users_id_fkey"; - -create policy "API_BRAIN_DEFINITION" -on "public"."api_brain_definition" -as permissive -for all -to service_role; - - -create policy "API_KEYS" -on "public"."api_keys" -as permissive -for all -to service_role; - - -create policy "BRAIN_SUBSCRIPTION_INVITATIONS" -on "public"."brain_subscription_invitations" -as permissive -for all -to service_role; - - -create policy "BRAINS" -on "public"."brains" -as permissive -for all -to service_role; - - -create policy "BRAINS_USERS" -on "public"."brains_users" -as permissive -for all -to service_role; - - -create policy "BRAINS_VECTORS" -on "public"."brains_vectors" -as permissive -for all -to service_role; - - -create policy "CHAT_HISTORY" -on "public"."chat_history" -as permissive -for all -to service_role; - - -create policy "CHATS" -on "public"."chats" -as permissive -for all -to service_role; - - -create policy "COMPOSITE_BRAIN_CONNECTIONS" -on "public"."composite_brain_connections" -as permissive -for all -to service_role; - - -create policy "KNOWLEDGE" -on "public"."knowledge" -as permissive -for all -to service_role; - - -create policy "MODELS" -on "public"."models" -as permissive -for all -to service_role; - - -create policy "NOTIFICATIONS" -on "public"."notifications" -as permissive -for all -to service_role; - - -create policy "NOTIFICATIONS" -on "public"."onboardings" -as permissive -for all -to service_role; - - -create policy "PRODUCT_TO_FEATURES" -on "public"."product_to_features" -as permissive -for all -to service_role; - - -create policy "PROMPTS" -on "public"."prompts" -as permissive -for all -to service_role; - - -create policy "USER_DAILY_USAGE" -on "public"."user_daily_usage" -as permissive -for all -to service_role; - - -create policy "USER_IDENTITY" -on "public"."user_identity" -as permissive -for all -to service_role; - - -create policy "USER_SETTINGS" -on "public"."user_settings" -as permissive -for all -to service_role; - - -create policy "USERS" -on "public"."users" -as permissive -for all -to public; - - -create policy "VECTORS" -on "public"."vectors" -as permissive -for all -to service_role; diff --git a/backend/supabase/migrations/20240119070124_search.sql b/backend/supabase/migrations/20240119070124_search.sql deleted file mode 100644 index 514957ef8..000000000 --- a/backend/supabase/migrations/20240119070124_search.sql +++ /dev/null @@ -1,25 +0,0 @@ -alter table "public"."brains" add column "meaning" vector; - -alter table "public"."brains" alter column "description" set default 'This needs to be changed'::text; - -alter table "public"."brains" alter column "description" set not null; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.match_brain(query_embedding vector, match_count integer) - RETURNS TABLE(id uuid, name text, similarity double precision) - LANGUAGE plpgsql -AS $function$ -#variable_conflict use_column -begin - return query - select - brain_id, - name, - 1 - (brains.meaning <=> query_embedding) as similarity - from brains - order by brains.meaning <=> query_embedding - limit match_count; -end; -$function$ -; diff --git a/backend/supabase/migrations/20240119222036_metadata.sql b/backend/supabase/migrations/20240119222036_metadata.sql deleted file mode 100644 index d139f5394..000000000 --- a/backend/supabase/migrations/20240119222036_metadata.sql +++ /dev/null @@ -1,3 +0,0 @@ -drop function if exists "public"."match_documents"(query_embedding vector, match_count integer); - -alter table "public"."chat_history" add column "metadata" jsonb; diff --git a/backend/supabase/migrations/20240120004107_tags.sql b/backend/supabase/migrations/20240120004107_tags.sql deleted file mode 100644 index fe6f2fdf6..000000000 --- a/backend/supabase/migrations/20240120004107_tags.sql +++ /dev/null @@ -1,3 +0,0 @@ -create type "public"."tags" as enum ('Finance', 'Legal', 'Health', 'Technology', 'Education', 'Resources', 'Marketing', 'Strategy', 'Operations', 'Compliance', 'Research', 'Innovation', 'Sustainability', 'Management', 'Communication', 'Data', 'Quality', 'Logistics', 'Policy', 'Design', 'Safety', 'Customer', 'Development', 'Reporting', 'Collaboration'); - -alter table "public"."brains" add column "tags" tags[]; diff --git a/backend/supabase/migrations/20240121195523_fix-public.sql b/backend/supabase/migrations/20240121195523_fix-public.sql deleted file mode 100644 index 0b664134a..000000000 --- a/backend/supabase/migrations/20240121195523_fix-public.sql +++ /dev/null @@ -1,32 +0,0 @@ -create type "public"."thumbs" as enum ('up', 'down'); - -drop function if exists "public"."match_brain"(query_embedding vector, match_count integer); - -alter table "public"."chat_history" add column "user_feedback" thumbs; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.match_brain(query_embedding vector, match_count integer, p_user_id uuid) - RETURNS TABLE(id uuid, name text, similarity double precision) - LANGUAGE plpgsql -AS $function$ -BEGIN - RETURN QUERY - SELECT - b.brain_id, - b.name, - 1 - (b.meaning <=> query_embedding) as similarity - FROM - brains b - LEFT JOIN - brains_users bu ON b.brain_id = bu.brain_id - WHERE - (b.status = 'public') OR - (bu.user_id = p_user_id AND bu.rights IN ('Owner', 'Editor', 'Viewer')) - ORDER BY - b.meaning <=> query_embedding - LIMIT - match_count; -END; -$function$ -; diff --git a/backend/supabase/migrations/20240122194117_monthly-credit.sql b/backend/supabase/migrations/20240122194117_monthly-credit.sql deleted file mode 100644 index b8ed42a0c..000000000 --- a/backend/supabase/migrations/20240122194117_monthly-credit.sql +++ /dev/null @@ -1,7 +0,0 @@ -alter table "public"."product_to_features" drop column "daily_chat_credit"; - -alter table "public"."product_to_features" add column "monthly_chat_credit" integer not null default 20; - -alter table "public"."user_settings" drop column "daily_chat_credit"; - -alter table "public"."user_settings" add column "monthly_chat_credit" integer default 100; diff --git a/backend/supabase/migrations/20240125230346_raw.sql b/backend/supabase/migrations/20240125230346_raw.sql deleted file mode 100644 index 5717dd73f..000000000 --- a/backend/supabase/migrations/20240125230346_raw.sql +++ /dev/null @@ -1,9 +0,0 @@ -alter table "public"."api_brain_definition" add column "jq_instructions" text not null default ''::text; - -alter table "public"."api_brain_definition" add column "raw" boolean not null default false; - -alter table "public"."api_brain_definition" alter column "brain_id" set not null; - -CREATE UNIQUE INDEX api_brain_definition_pkey ON public.api_brain_definition USING btree (brain_id); - -alter table "public"."api_brain_definition" add constraint "api_brain_definition_pkey" PRIMARY KEY using index "api_brain_definition_pkey"; diff --git a/backend/supabase/migrations/20240206040636_notion.sql b/backend/supabase/migrations/20240206040636_notion.sql deleted file mode 100644 index 33873acd6..000000000 --- a/backend/supabase/migrations/20240206040636_notion.sql +++ /dev/null @@ -1,163 +0,0 @@ -alter table "public"."brains" alter column "brain_type" drop default; - -alter type "public"."brain_type_enum" rename to "brain_type_enum__old_version_to_be_dropped"; - -create type "public"."brain_type_enum" as enum ('doc', 'api', 'composite', 'integration'); - -create table "public"."integrations" ( - "created_at" timestamp with time zone not null default now(), - "integration_name" text not null, - "integration_logo_url" text, - "connection_settings" jsonb, - "id" uuid not null default gen_random_uuid() -); - - -alter table "public"."integrations" enable row level security; - -create table "public"."integrations_user" ( - "id" bigint generated by default as identity not null, - "created_at" timestamp with time zone not null default now(), - "user_id" uuid not null, - "brain_id" uuid, - "integration_id" uuid, - "settings" jsonb, - "credentials" jsonb -); - - -alter table "public"."integrations_user" enable row level security; - -alter table "public"."brains" alter column brain_type type "public"."brain_type_enum" using brain_type::text::"public"."brain_type_enum"; - -alter table "public"."brains" alter column "brain_type" set default 'doc'::brain_type_enum; - -drop type "public"."brain_type_enum__old_version_to_be_dropped"; - -alter table "public"."brains" alter column "model" set default 'gpt-3.5-turbo-1106'::text; - -alter table "public"."brains_vectors" add column "id" uuid not null default gen_random_uuid(); - -alter table "public"."brains_vectors" alter column "vector_id" set not null; - -alter table "public"."knowledge" add column "integration" text; - -alter table "public"."knowledge" add column "integration_link" text; - -alter table "public"."user_settings" alter column "models" set default '["gpt-3.5-turbo-0125"]'::jsonb; - -CREATE UNIQUE INDEX brains_vectors_pkey ON public.brains_vectors USING btree (id); - -CREATE UNIQUE INDEX integrations_id_key ON public.integrations USING btree (id); - -CREATE UNIQUE INDEX integrations_integration_name_key ON public.integrations USING btree (integration_name); - -CREATE UNIQUE INDEX integrations_pkey ON public.integrations USING btree (id); - -CREATE UNIQUE INDEX integrations_user_pkey ON public.integrations_user USING btree (id); - -alter table "public"."brains_vectors" add constraint "brains_vectors_pkey" PRIMARY KEY using index "brains_vectors_pkey"; - -alter table "public"."integrations" add constraint "integrations_pkey" PRIMARY KEY using index "integrations_pkey"; - -alter table "public"."integrations_user" add constraint "integrations_user_pkey" PRIMARY KEY using index "integrations_user_pkey"; - -alter table "public"."integrations" add constraint "integrations_id_key" UNIQUE using index "integrations_id_key"; - -alter table "public"."integrations" add constraint "integrations_integration_name_key" UNIQUE using index "integrations_integration_name_key"; - -alter table "public"."integrations_user" add constraint "integrations_user_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."integrations_user" validate constraint "integrations_user_brain_id_fkey"; - -alter table "public"."integrations_user" add constraint "integrations_user_integration_id_fkey" FOREIGN KEY (integration_id) REFERENCES integrations(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."integrations_user" validate constraint "integrations_user_integration_id_fkey"; - -alter table "public"."integrations_user" add constraint "integrations_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."integrations_user" validate constraint "integrations_user_user_id_fkey"; - -grant delete on table "public"."integrations" to "anon"; - -grant insert on table "public"."integrations" to "anon"; - -grant references on table "public"."integrations" to "anon"; - -grant select on table "public"."integrations" to "anon"; - -grant trigger on table "public"."integrations" to "anon"; - -grant truncate on table "public"."integrations" to "anon"; - -grant update on table "public"."integrations" to "anon"; - -grant delete on table "public"."integrations" to "authenticated"; - -grant insert on table "public"."integrations" to "authenticated"; - -grant references on table "public"."integrations" to "authenticated"; - -grant select on table "public"."integrations" to "authenticated"; - -grant trigger on table "public"."integrations" to "authenticated"; - -grant truncate on table "public"."integrations" to "authenticated"; - -grant update on table "public"."integrations" to "authenticated"; - -grant delete on table "public"."integrations" to "service_role"; - -grant insert on table "public"."integrations" to "service_role"; - -grant references on table "public"."integrations" to "service_role"; - -grant select on table "public"."integrations" to "service_role"; - -grant trigger on table "public"."integrations" to "service_role"; - -grant truncate on table "public"."integrations" to "service_role"; - -grant update on table "public"."integrations" to "service_role"; - -grant delete on table "public"."integrations_user" to "anon"; - -grant insert on table "public"."integrations_user" to "anon"; - -grant references on table "public"."integrations_user" to "anon"; - -grant select on table "public"."integrations_user" to "anon"; - -grant trigger on table "public"."integrations_user" to "anon"; - -grant truncate on table "public"."integrations_user" to "anon"; - -grant update on table "public"."integrations_user" to "anon"; - -grant delete on table "public"."integrations_user" to "authenticated"; - -grant insert on table "public"."integrations_user" to "authenticated"; - -grant references on table "public"."integrations_user" to "authenticated"; - -grant select on table "public"."integrations_user" to "authenticated"; - -grant trigger on table "public"."integrations_user" to "authenticated"; - -grant truncate on table "public"."integrations_user" to "authenticated"; - -grant update on table "public"."integrations_user" to "authenticated"; - -grant delete on table "public"."integrations_user" to "service_role"; - -grant insert on table "public"."integrations_user" to "service_role"; - -grant references on table "public"."integrations_user" to "service_role"; - -grant select on table "public"."integrations_user" to "service_role"; - -grant trigger on table "public"."integrations_user" to "service_role"; - -grant truncate on table "public"."integrations_user" to "service_role"; - -grant update on table "public"."integrations_user" to "service_role"; diff --git a/backend/supabase/migrations/20240207034043_related.sql b/backend/supabase/migrations/20240207034043_related.sql deleted file mode 100644 index 2933515e3..000000000 --- a/backend/supabase/migrations/20240207034043_related.sql +++ /dev/null @@ -1,37 +0,0 @@ -alter table "public"."brains" drop constraint "brains_prompt_id_fkey"; - -alter table "public"."chat_history" drop constraint "chat_history_prompt_id_fkey"; - -alter table "public"."brains" add constraint "brains_prompt_id_fkey" FOREIGN KEY (prompt_id) REFERENCES prompts(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."brains" validate constraint "brains_prompt_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_prompt_id_fkey" FOREIGN KEY (prompt_id) REFERENCES prompts(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."chat_history" validate constraint "chat_history_prompt_id_fkey"; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.match_brain(query_embedding vector, match_count integer, p_user_id uuid) - RETURNS TABLE(id uuid, name text, similarity double precision) - LANGUAGE plpgsql -AS $function$ -BEGIN - RETURN QUERY - SELECT - b.brain_id, - b.name, - 1 - (b.meaning <=> query_embedding) as similarity - FROM - brains b - LEFT JOIN - brains_users bu ON b.brain_id = bu.brain_id - WHERE - (bu.user_id = p_user_id AND bu.rights IN ('Owner', 'Editor', 'Viewer')) - ORDER BY - b.meaning <=> query_embedding - LIMIT - match_count; -END; -$function$ -; diff --git a/backend/supabase/migrations/20240207071108_chunk.sql b/backend/supabase/migrations/20240207071108_chunk.sql deleted file mode 100644 index 67e1db794..000000000 --- a/backend/supabase/migrations/20240207071108_chunk.sql +++ /dev/null @@ -1,59 +0,0 @@ -alter table "public"."brains_vectors" drop constraint "brains_vectors_vector_id_fkey"; - -drop function if exists "public"."match_vectors"(query_embedding vector, match_count integer, p_brain_id uuid); - -CREATE INDEX vectors_metadata_idx ON public.vectors USING gin (metadata); - -alter table "public"."brains_vectors" add constraint "brains_vectors_vector_id_fkey" FOREIGN KEY (vector_id) REFERENCES vectors(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."brains_vectors" validate constraint "brains_vectors_vector_id_fkey"; - -set check_function_bodies = off; - -CREATE OR REPLACE FUNCTION public.match_vectors(query_embedding vector, p_brain_id uuid, max_chunk_sum integer) - RETURNS TABLE(id uuid, brain_id uuid, content text, metadata jsonb, embedding vector, similarity double precision) - LANGUAGE plpgsql -AS $function$ -BEGIN - RETURN QUERY - WITH ranked_vectors AS ( - SELECT - v.id AS vector_id, -- Explicitly qualified - bv.brain_id AS vector_brain_id, -- Explicitly qualified and aliased - v.content AS vector_content, -- Explicitly qualified and aliased - v.metadata AS vector_metadata, -- Explicitly qualified and aliased - v.embedding AS vector_embedding, -- Explicitly qualified and aliased - 1 - (v.embedding <=> query_embedding) AS calculated_similarity, -- Calculated and aliased - (v.metadata->>'chunk_size')::integer AS chunk_size -- Explicitly qualified - FROM - vectors v - INNER JOIN - brains_vectors bv ON v.id = bv.vector_id - WHERE - bv.brain_id = p_brain_id - ORDER BY - calculated_similarity -- Aliased similarity - ), filtered_vectors AS ( - SELECT - vector_id, - vector_brain_id, - vector_content, - vector_metadata, - vector_embedding, - calculated_similarity, - chunk_size, - sum(chunk_size) OVER (ORDER BY calculated_similarity) AS running_total - FROM ranked_vectors - ) - SELECT - vector_id AS id, - vector_brain_id AS brain_id, - vector_content AS content, - vector_metadata AS metadata, - vector_embedding AS embedding, - calculated_similarity AS similarity - FROM filtered_vectors - WHERE running_total <= max_chunk_sum; -END; -$function$ -; diff --git a/backend/supabase/migrations/20240216192826_integration.sql b/backend/supabase/migrations/20240216192826_integration.sql deleted file mode 100644 index 582fb877e..000000000 --- a/backend/supabase/migrations/20240216192826_integration.sql +++ /dev/null @@ -1,7 +0,0 @@ -create type "public"."integration_type" as enum ('custom', 'sync'); - -alter table "public"."integrations" add column "description" text not null default 'Default description'::text; - -alter table "public"."integrations" add column "integration_type" integration_type not null default 'custom'::integration_type; - -alter table "public"."integrations" add column "max_files" integer not null default 0; diff --git a/backend/supabase/migrations/20240228182948_notion.sql b/backend/supabase/migrations/20240228182948_notion.sql deleted file mode 100644 index c031df8a7..000000000 --- a/backend/supabase/migrations/20240228182948_notion.sql +++ /dev/null @@ -1,42 +0,0 @@ -alter table "public"."brain_subscription_invitations" drop constraint "brain_subscription_invitations_brain_id_fkey"; - -alter table "public"."brains_users" drop constraint "brains_users_brain_id_fkey"; - -alter table "public"."brains_vectors" drop constraint "brains_vectors_brain_id_fkey"; - -alter table "public"."chat_history" drop constraint "chat_history_brain_id_fkey"; - -alter table "public"."knowledge" drop constraint "knowledge_brain_id_fkey"; - -alter table "public"."knowledge" drop constraint "knowledge_pkey"; - -drop index if exists "public"."knowledge_pkey"; - -alter table "public"."integrations_user" add column "last_synced" timestamp with time zone default now(); - -alter table "public"."brain_subscription_invitations" add constraint "brain_subscription_invitations_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."brain_subscription_invitations" validate constraint "brain_subscription_invitations_brain_id_fkey"; - -alter table "public"."brains_users" add constraint "brains_users_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."brains_users" validate constraint "brains_users_brain_id_fkey"; - -alter table "public"."brains_vectors" add constraint "brains_vectors_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."brains_vectors" validate constraint "brains_vectors_brain_id_fkey"; - -alter table "public"."chat_history" add constraint "chat_history_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."chat_history" validate constraint "chat_history_brain_id_fkey"; - -alter table "public"."knowledge" add constraint "knowledge_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."knowledge" validate constraint "knowledge_brain_id_fkey"; - -create policy "Enable all for service role" -on "public"."integrations_user" -as permissive -for all -to service_role -with check (true); diff --git a/backend/supabase/migrations/20240304223646_integrations-all.sql b/backend/supabase/migrations/20240304223646_integrations-all.sql deleted file mode 100644 index 90307ab9b..000000000 --- a/backend/supabase/migrations/20240304223646_integrations-all.sql +++ /dev/null @@ -1,11 +0,0 @@ -alter table "public"."integrations" alter column "integration_type" drop default; - -alter type "public"."integration_type" rename to "integration_type__old_version_to_be_dropped"; - -create type "public"."integration_type" as enum ('custom', 'sync', 'doc'); - -alter table "public"."integrations" alter column integration_type type "public"."integration_type" using integration_type::text::"public"."integration_type"; - -alter table "public"."integrations" alter column "integration_type" set default 'custom'::integration_type; - -drop type "public"."integration_type__old_version_to_be_dropped"; diff --git a/backend/supabase/migrations/20240305225452_tags-integration.sql b/backend/supabase/migrations/20240305225452_tags-integration.sql deleted file mode 100644 index 20efd2c44..000000000 --- a/backend/supabase/migrations/20240305225452_tags-integration.sql +++ /dev/null @@ -1,5 +0,0 @@ -create type "public"."brain_tags" as enum ('new', 'recommended', 'most_popular', 'premium', 'coming_soon', 'community', 'deprecated'); - -alter table "public"."integrations" add column "information" text; - -alter table "public"."integrations" add column "tags" brain_tags[]; diff --git a/backend/supabase/migrations/20240306013910_allow_model_change.sql b/backend/supabase/migrations/20240306013910_allow_model_change.sql deleted file mode 100644 index 0c112e02a..000000000 --- a/backend/supabase/migrations/20240306013910_allow_model_change.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."integrations" add column "allow_model_change" boolean not null default true; diff --git a/backend/supabase/migrations/20240306205133_integration_display_name.sql b/backend/supabase/migrations/20240306205133_integration_display_name.sql deleted file mode 100644 index d9aa394fb..000000000 --- a/backend/supabase/migrations/20240306205133_integration_display_name.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."integrations" add column "integration_display_name" text not null default 'Brain'::text; diff --git a/backend/supabase/migrations/20240313024244_onboarding-user.sql b/backend/supabase/migrations/20240313024244_onboarding-user.sql deleted file mode 100644 index 30e5053bd..000000000 --- a/backend/supabase/migrations/20240313024244_onboarding-user.sql +++ /dev/null @@ -1,13 +0,0 @@ -alter table "public"."user_identity" drop constraint "user_identity_user_id_fkey"; - -alter table "public"."user_identity" add column "company" text; - -alter table "public"."user_identity" add column "onboarded" boolean not null default false; - -alter table "public"."user_identity" add column "username" text; - -alter table "public"."users" add column "onboarded" boolean not null default false; - -alter table "public"."user_identity" add constraint "public_user_identity_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."user_identity" validate constraint "public_user_identity_user_id_fkey"; diff --git a/backend/supabase/migrations/20240314005817_user_identity_company_info.sql b/backend/supabase/migrations/20240314005817_user_identity_company_info.sql deleted file mode 100644 index 488a79241..000000000 --- a/backend/supabase/migrations/20240314005817_user_identity_company_info.sql +++ /dev/null @@ -1,5 +0,0 @@ -create type "public"."user_identity_company_size" as enum ('1-10', '10-25', '25-50', '50-100', '100-250', '250-500', '500-1000', '1000-5000', '+5000'); - -alter table "public"."user_identity" add column "company_size" user_identity_company_size; - -alter table "public"."user_identity" add column "role_in_company" text; diff --git a/backend/supabase/migrations/20240316195514_usage_purpose.sql b/backend/supabase/migrations/20240316195514_usage_purpose.sql deleted file mode 100644 index 861f3cc62..000000000 --- a/backend/supabase/migrations/20240316195514_usage_purpose.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."user_identity" drop column "role_in_company"; - -alter table "public"."user_identity" add column "usage_purpose" text; diff --git a/backend/supabase/migrations/20240318024425_rename_feedback_to_thumbs.sql b/backend/supabase/migrations/20240318024425_rename_feedback_to_thumbs.sql deleted file mode 100644 index 2eeb6ab95..000000000 --- a/backend/supabase/migrations/20240318024425_rename_feedback_to_thumbs.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."chat_history" drop column "user_feedback"; - -alter table "public"."chat_history" add column "thumbs" boolean; diff --git a/backend/supabase/migrations/20240320215813_fix_match_vector_function.sql b/backend/supabase/migrations/20240320215813_fix_match_vector_function.sql deleted file mode 100644 index aef608832..000000000 --- a/backend/supabase/migrations/20240320215813_fix_match_vector_function.sql +++ /dev/null @@ -1,50 +0,0 @@ -set check_function_bodies = off; - - -CREATE OR REPLACE FUNCTION public.match_vectors(query_embedding vector, p_brain_id uuid, max_chunk_sum integer) - RETURNS TABLE(id uuid, brain_id uuid, content text, metadata jsonb, embedding vector, similarity double precision) - LANGUAGE plpgsql -AS $function$ -BEGIN - RETURN QUERY - WITH ranked_vectors AS ( - SELECT - v.id AS vector_id, -- Explicitly qualified - bv.brain_id AS vector_brain_id, -- Explicitly qualified and aliased - v.content AS vector_content, -- Explicitly qualified and aliased - v.metadata AS vector_metadata, -- Explicitly qualified and aliased - v.embedding AS vector_embedding, -- Explicitly qualified and aliased - 1 - (v.embedding <=> query_embedding) AS calculated_similarity, -- Calculated and aliased - (v.metadata->>'chunk_size')::integer AS chunk_size -- Explicitly qualified - FROM - vectors v - INNER JOIN - brains_vectors bv ON v.id = bv.vector_id - WHERE - bv.brain_id = p_brain_id - ORDER BY - calculated_similarity -- Aliased similarity - ), filtered_vectors AS ( - SELECT - vector_id, - vector_brain_id, - vector_content, - vector_metadata, - vector_embedding, - calculated_similarity, - chunk_size, - sum(chunk_size) OVER (ORDER BY calculated_similarity DESC) AS running_total - FROM ranked_vectors - ) - SELECT - vector_id AS id, - vector_brain_id AS brain_id, - vector_content AS content, - vector_metadata AS metadata, - vector_embedding AS embedding, - calculated_similarity AS similarity - FROM filtered_vectors - WHERE running_total <= max_chunk_sum; -END; -$function$ -; diff --git a/backend/supabase/migrations/20240329212126_onboarding_brain.sql b/backend/supabase/migrations/20240329212126_onboarding_brain.sql deleted file mode 100644 index 1c7e9e4e9..000000000 --- a/backend/supabase/migrations/20240329212126_onboarding_brain.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."integrations" add column "onboarding_brain" boolean default false; diff --git a/backend/supabase/migrations/20240330233534_ingestion.sql b/backend/supabase/migrations/20240330233534_ingestion.sql deleted file mode 100644 index 836759b2c..000000000 --- a/backend/supabase/migrations/20240330233534_ingestion.sql +++ /dev/null @@ -1,66 +0,0 @@ -create table "public"."ingestions" ( - "name" text, - "id" uuid not null default gen_random_uuid() -); - - -alter table "public"."ingestions" enable row level security; - -CREATE UNIQUE INDEX ingestions_pkey ON public.ingestions USING btree (id); - -alter table "public"."ingestions" add constraint "ingestions_pkey" PRIMARY KEY using index "ingestions_pkey"; - -grant delete on table "public"."ingestions" to "anon"; - -grant insert on table "public"."ingestions" to "anon"; - -grant references on table "public"."ingestions" to "anon"; - -grant select on table "public"."ingestions" to "anon"; - -grant trigger on table "public"."ingestions" to "anon"; - -grant truncate on table "public"."ingestions" to "anon"; - -grant update on table "public"."ingestions" to "anon"; - -grant delete on table "public"."ingestions" to "authenticated"; - -grant insert on table "public"."ingestions" to "authenticated"; - -grant references on table "public"."ingestions" to "authenticated"; - -grant select on table "public"."ingestions" to "authenticated"; - -grant trigger on table "public"."ingestions" to "authenticated"; - -grant truncate on table "public"."ingestions" to "authenticated"; - -grant update on table "public"."ingestions" to "authenticated"; - -grant delete on table "public"."ingestions" to "service_role"; - -grant insert on table "public"."ingestions" to "service_role"; - -grant references on table "public"."ingestions" to "service_role"; - -grant select on table "public"."ingestions" to "service_role"; - -grant trigger on table "public"."ingestions" to "service_role"; - -grant truncate on table "public"."ingestions" to "service_role"; - -grant update on table "public"."ingestions" to "service_role"; - -create policy "INGESTION" -on "public"."ingestions" -as permissive -for all -to service_role; - - -create policy "INTEGRATIONS" -on "public"."integrations" -as permissive -for all -to service_role; diff --git a/backend/supabase/migrations/20240402005455_ingestion.sql b/backend/supabase/migrations/20240402005455_ingestion.sql deleted file mode 100644 index 70168f3b0..000000000 --- a/backend/supabase/migrations/20240402005455_ingestion.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."ingestions" add column "brain_id_required" boolean not null default true; - -alter table "public"."ingestions" add column "file_1_required" boolean not null default false; diff --git a/backend/supabase/migrations/20240402013303_ingestion_url.sql b/backend/supabase/migrations/20240402013303_ingestion_url.sql deleted file mode 100644 index cdd1cd670..000000000 --- a/backend/supabase/migrations/20240402013303_ingestion_url.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."ingestions" add column "url_required" boolean default false; diff --git a/backend/supabase/migrations/20240402015128_seed-ingestions.sql b/backend/supabase/migrations/20240402015128_seed-ingestions.sql deleted file mode 100644 index 6d34cb68b..000000000 --- a/backend/supabase/migrations/20240402015128_seed-ingestions.sql +++ /dev/null @@ -1,17 +0,0 @@ -INSERT INTO "public"."ingestions" ("name", "id", "brain_id_required", "file_1_required", "url_required") -SELECT 'summary', 'bc414385-342e-49fc-b252-3529c935c63a', true, true, false -WHERE NOT EXISTS ( - SELECT 1 FROM "public"."ingestions" WHERE "id" = 'bc414385-342e-49fc-b252-3529c935c63a' -); - -INSERT INTO "public"."ingestions" ("name", "id", "brain_id_required", "file_1_required", "url_required") -SELECT 'audio_transcript', 'c502a10d-5a98-40f9-9699-a65d7ece37de', true, true, false -WHERE NOT EXISTS ( - SELECT 1 FROM "public"."ingestions" WHERE "id" = 'c502a10d-5a98-40f9-9699-a65d7ece37de' -); - -INSERT INTO "public"."ingestions" ("name", "id", "brain_id_required", "file_1_required", "url_required") -SELECT 'crawler', '948ae685-5710-4dde-bb80-36ce0097ca7b', true, false, true -WHERE NOT EXISTS ( - SELECT 1 FROM "public"."ingestions" WHERE "id" = '948ae685-5710-4dde-bb80-36ce0097ca7b' -); diff --git a/backend/supabase/migrations/20240410112108_assistant.sql b/backend/supabase/migrations/20240410112108_assistant.sql deleted file mode 100644 index 5c22cfb01..000000000 --- a/backend/supabase/migrations/20240410112108_assistant.sql +++ /dev/null @@ -1,112 +0,0 @@ -drop policy "INGESTION" on "public"."ingestions"; - -revoke delete on table "public"."ingestions" from "anon"; - -revoke insert on table "public"."ingestions" from "anon"; - -revoke references on table "public"."ingestions" from "anon"; - -revoke select on table "public"."ingestions" from "anon"; - -revoke trigger on table "public"."ingestions" from "anon"; - -revoke truncate on table "public"."ingestions" from "anon"; - -revoke update on table "public"."ingestions" from "anon"; - -revoke delete on table "public"."ingestions" from "authenticated"; - -revoke insert on table "public"."ingestions" from "authenticated"; - -revoke references on table "public"."ingestions" from "authenticated"; - -revoke select on table "public"."ingestions" from "authenticated"; - -revoke trigger on table "public"."ingestions" from "authenticated"; - -revoke truncate on table "public"."ingestions" from "authenticated"; - -revoke update on table "public"."ingestions" from "authenticated"; - -revoke delete on table "public"."ingestions" from "service_role"; - -revoke insert on table "public"."ingestions" from "service_role"; - -revoke references on table "public"."ingestions" from "service_role"; - -revoke select on table "public"."ingestions" from "service_role"; - -revoke trigger on table "public"."ingestions" from "service_role"; - -revoke truncate on table "public"."ingestions" from "service_role"; - -revoke update on table "public"."ingestions" from "service_role"; - -alter table "public"."ingestions" drop constraint "ingestions_pkey"; - -drop index if exists "public"."ingestions_pkey"; - -drop table "public"."ingestions"; - -create table "public"."assistants" ( - "name" text, - "id" uuid not null default gen_random_uuid(), - "brain_id_required" boolean not null default true, - "file_1_required" boolean not null default false, - "url_required" boolean default false -); - - -alter table "public"."assistants" enable row level security; - -CREATE UNIQUE INDEX ingestions_pkey ON public.assistants USING btree (id); - -alter table "public"."assistants" add constraint "ingestions_pkey" PRIMARY KEY using index "ingestions_pkey"; - -grant delete on table "public"."assistants" to "anon"; - -grant insert on table "public"."assistants" to "anon"; - -grant references on table "public"."assistants" to "anon"; - -grant select on table "public"."assistants" to "anon"; - -grant trigger on table "public"."assistants" to "anon"; - -grant truncate on table "public"."assistants" to "anon"; - -grant update on table "public"."assistants" to "anon"; - -grant delete on table "public"."assistants" to "authenticated"; - -grant insert on table "public"."assistants" to "authenticated"; - -grant references on table "public"."assistants" to "authenticated"; - -grant select on table "public"."assistants" to "authenticated"; - -grant trigger on table "public"."assistants" to "authenticated"; - -grant truncate on table "public"."assistants" to "authenticated"; - -grant update on table "public"."assistants" to "authenticated"; - -grant delete on table "public"."assistants" to "service_role"; - -grant insert on table "public"."assistants" to "service_role"; - -grant references on table "public"."assistants" to "service_role"; - -grant select on table "public"."assistants" to "service_role"; - -grant trigger on table "public"."assistants" to "service_role"; - -grant truncate on table "public"."assistants" to "service_role"; - -grant update on table "public"."assistants" to "service_role"; - -create policy "INGESTION" -on "public"."assistants" -as permissive -for all -to service_role; diff --git a/backend/supabase/migrations/20240501180719_notifications.sql b/backend/supabase/migrations/20240501180719_notifications.sql deleted file mode 100644 index 43f730847..000000000 --- a/backend/supabase/migrations/20240501180719_notifications.sql +++ /dev/null @@ -1,32 +0,0 @@ -create type "public"."status" as enum ('info', 'warning', 'success', 'error'); - -alter table "public"."notifications" drop column "action"; - -alter table "public"."notifications" drop column "chat_id"; - -alter table "public"."notifications" drop column "message"; - -alter table "public"."notifications" add column "archived" boolean not null default false; - -alter table "public"."notifications" add column "description" text; - -alter table "public"."notifications" add column "read" boolean not null default false; - -alter table "public"."notifications" add column "title" text not null; - -alter table "public"."notifications" add column "user_id" uuid not null; - -alter table "public"."notifications" alter column "status" set default 'info'::status; - -alter table "public"."notifications" alter column "status" set data type status using "status"::status; - -alter table "public"."notifications" add constraint "public_notifications_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."notifications" validate constraint "public_notifications_user_id_fkey"; - -create policy "allow_user_all_notifications" -on "public"."notifications" -as permissive -for all -to public -using ((user_id = auth.uid())); diff --git a/backend/supabase/migrations/20240506150059_timestampz.sql b/backend/supabase/migrations/20240506150059_timestampz.sql deleted file mode 100644 index 4dcecd12e..000000000 --- a/backend/supabase/migrations/20240506150059_timestampz.sql +++ /dev/null @@ -1,6 +0,0 @@ -alter table "public"."notifications" alter column "datetime" set default (now() AT TIME ZONE 'utc'::text); - -alter table "public"."notifications" alter column "datetime" set data type timestamp with time zone using "datetime"::timestamp with time zone; - -alter - publication supabase_realtime add table notifications diff --git a/backend/supabase/migrations/20240514080520_rls_optim.sql b/backend/supabase/migrations/20240514080520_rls_optim.sql deleted file mode 100644 index cbb000b58..000000000 --- a/backend/supabase/migrations/20240514080520_rls_optim.sql +++ /dev/null @@ -1,8 +0,0 @@ -drop policy "allow_user_all_notifications" on "public"."notifications"; - -create policy "allow_user_all_notifications" -on "public"."notifications" -as permissive -for all -to public -using ((user_id = ( SELECT auth.uid() AS uid))); diff --git a/backend/supabase/migrations/20240516143634_syncs.sql b/backend/supabase/migrations/20240516143634_syncs.sql deleted file mode 100644 index 3ceee412b..000000000 --- a/backend/supabase/migrations/20240516143634_syncs.sql +++ /dev/null @@ -1,128 +0,0 @@ -create table "public"."syncs_active" ( - "id" bigint generated by default as identity not null, - "name" text not null, - "syncs_user_id" bigint not null, - "user_id" uuid not null default gen_random_uuid(), - "settings" jsonb, - "last_synced" timestamp with time zone not null default (now() AT TIME ZONE 'utc'::text), - "sync_interval_minutes" integer default 360 -); - - -alter table "public"."syncs_active" enable row level security; - -create table "public"."syncs_user" ( - "id" bigint generated by default as identity not null, - "name" text not null, - "provider" text not null, - "state" jsonb, - "credentials" jsonb, - "user_id" uuid default gen_random_uuid() -); - - -alter table "public"."syncs_user" enable row level security; - -CREATE UNIQUE INDEX syncs_active_pkey ON public.syncs_active USING btree (id); - -CREATE UNIQUE INDEX syncs_user_pkey ON public.syncs_user USING btree (id); - -alter table "public"."syncs_active" add constraint "syncs_active_pkey" PRIMARY KEY using index "syncs_active_pkey"; - -alter table "public"."syncs_user" add constraint "syncs_user_pkey" PRIMARY KEY using index "syncs_user_pkey"; - -alter table "public"."syncs_active" add constraint "public_syncs_active_syncs_user_id_fkey" FOREIGN KEY (syncs_user_id) REFERENCES syncs_user(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_active" validate constraint "public_syncs_active_syncs_user_id_fkey"; - -alter table "public"."syncs_active" add constraint "public_syncs_active_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_active" validate constraint "public_syncs_active_user_id_fkey"; - -alter table "public"."syncs_user" add constraint "public_syncs_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_user" validate constraint "public_syncs_user_user_id_fkey"; - -grant delete on table "public"."syncs_active" to "anon"; - -grant insert on table "public"."syncs_active" to "anon"; - -grant references on table "public"."syncs_active" to "anon"; - -grant select on table "public"."syncs_active" to "anon"; - -grant trigger on table "public"."syncs_active" to "anon"; - -grant truncate on table "public"."syncs_active" to "anon"; - -grant update on table "public"."syncs_active" to "anon"; - -grant delete on table "public"."syncs_active" to "authenticated"; - -grant insert on table "public"."syncs_active" to "authenticated"; - -grant references on table "public"."syncs_active" to "authenticated"; - -grant select on table "public"."syncs_active" to "authenticated"; - -grant trigger on table "public"."syncs_active" to "authenticated"; - -grant truncate on table "public"."syncs_active" to "authenticated"; - -grant update on table "public"."syncs_active" to "authenticated"; - -grant delete on table "public"."syncs_active" to "service_role"; - -grant insert on table "public"."syncs_active" to "service_role"; - -grant references on table "public"."syncs_active" to "service_role"; - -grant select on table "public"."syncs_active" to "service_role"; - -grant trigger on table "public"."syncs_active" to "service_role"; - -grant truncate on table "public"."syncs_active" to "service_role"; - -grant update on table "public"."syncs_active" to "service_role"; - -grant delete on table "public"."syncs_user" to "anon"; - -grant insert on table "public"."syncs_user" to "anon"; - -grant references on table "public"."syncs_user" to "anon"; - -grant select on table "public"."syncs_user" to "anon"; - -grant trigger on table "public"."syncs_user" to "anon"; - -grant truncate on table "public"."syncs_user" to "anon"; - -grant update on table "public"."syncs_user" to "anon"; - -grant delete on table "public"."syncs_user" to "authenticated"; - -grant insert on table "public"."syncs_user" to "authenticated"; - -grant references on table "public"."syncs_user" to "authenticated"; - -grant select on table "public"."syncs_user" to "authenticated"; - -grant trigger on table "public"."syncs_user" to "authenticated"; - -grant truncate on table "public"."syncs_user" to "authenticated"; - -grant update on table "public"."syncs_user" to "authenticated"; - -grant delete on table "public"."syncs_user" to "service_role"; - -grant insert on table "public"."syncs_user" to "service_role"; - -grant references on table "public"."syncs_user" to "service_role"; - -grant select on table "public"."syncs_user" to "service_role"; - -grant trigger on table "public"."syncs_user" to "service_role"; - -grant truncate on table "public"."syncs_user" to "service_role"; - -grant update on table "public"."syncs_user" to "service_role"; diff --git a/backend/supabase/migrations/20240521144817_syncs-files.sql b/backend/supabase/migrations/20240521144817_syncs-files.sql deleted file mode 100644 index 80f70fa97..000000000 --- a/backend/supabase/migrations/20240521144817_syncs-files.sql +++ /dev/null @@ -1,83 +0,0 @@ -create table "public"."syncs_files" ( - "id" bigint generated by default as identity not null, - "syncs_active_id" bigint not null, - "last_modified" timestamp with time zone not null default (now() AT TIME ZONE 'utc'::text), - "brain_id" uuid default gen_random_uuid(), - "path" text not null -); - - -alter table "public"."syncs_files" enable row level security; - -alter table "public"."syncs_active" add column "brain_id" uuid; - -CREATE UNIQUE INDEX sync_files_pkey ON public.syncs_files USING btree (id); - -alter table "public"."syncs_files" add constraint "sync_files_pkey" PRIMARY KEY using index "sync_files_pkey"; - -alter table "public"."syncs_active" add constraint "public_syncs_active_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_active" validate constraint "public_syncs_active_brain_id_fkey"; - -alter table "public"."syncs_files" add constraint "public_sync_files_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_files" validate constraint "public_sync_files_brain_id_fkey"; - -alter table "public"."syncs_files" add constraint "public_sync_files_sync_active_id_fkey" FOREIGN KEY (syncs_active_id) REFERENCES syncs_active(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."syncs_files" validate constraint "public_sync_files_sync_active_id_fkey"; - -grant delete on table "public"."syncs_files" to "anon"; - -grant insert on table "public"."syncs_files" to "anon"; - -grant references on table "public"."syncs_files" to "anon"; - -grant select on table "public"."syncs_files" to "anon"; - -grant trigger on table "public"."syncs_files" to "anon"; - -grant truncate on table "public"."syncs_files" to "anon"; - -grant update on table "public"."syncs_files" to "anon"; - -grant delete on table "public"."syncs_files" to "authenticated"; - -grant insert on table "public"."syncs_files" to "authenticated"; - -grant references on table "public"."syncs_files" to "authenticated"; - -grant select on table "public"."syncs_files" to "authenticated"; - -grant trigger on table "public"."syncs_files" to "authenticated"; - -grant truncate on table "public"."syncs_files" to "authenticated"; - -grant update on table "public"."syncs_files" to "authenticated"; - -grant delete on table "public"."syncs_files" to "service_role"; - -grant insert on table "public"."syncs_files" to "service_role"; - -grant references on table "public"."syncs_files" to "service_role"; - -grant select on table "public"."syncs_files" to "service_role"; - -grant trigger on table "public"."syncs_files" to "service_role"; - -grant truncate on table "public"."syncs_files" to "service_role"; - -grant update on table "public"."syncs_files" to "service_role"; - -create policy "syncs_active" -on "public"."syncs_active" -as permissive -for all -to service_role; - - -create policy "syncs_user" -on "public"."syncs_user" -as permissive -for all -to service_role; diff --git a/backend/supabase/migrations/20240529132812_syncs-emails.sql b/backend/supabase/migrations/20240529132812_syncs-emails.sql deleted file mode 100644 index 83c569665..000000000 --- a/backend/supabase/migrations/20240529132812_syncs-emails.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."syncs_user" add column "email" text; diff --git a/backend/supabase/migrations/20240606170930_sync-reduce-time.sql b/backend/supabase/migrations/20240606170930_sync-reduce-time.sql deleted file mode 100644 index 8451be50d..000000000 --- a/backend/supabase/migrations/20240606170930_sync-reduce-time.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."syncs_active" alter column "last_synced" set default '2024-06-01 15:30:25+00'::timestamp with time zone; diff --git a/backend/supabase/migrations/20240608095352_supported-sync.sql b/backend/supabase/migrations/20240608095352_supported-sync.sql deleted file mode 100644 index 70625dbc5..000000000 --- a/backend/supabase/migrations/20240608095352_supported-sync.sql +++ /dev/null @@ -1,2 +0,0 @@ -alter table "public"."syncs_files" add column "supported" boolean not null default true; - diff --git a/backend/supabase/migrations/20240610141546_force-sync.sql b/backend/supabase/migrations/20240610141546_force-sync.sql deleted file mode 100644 index 251750fcb..000000000 --- a/backend/supabase/migrations/20240610141546_force-sync.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."syncs_active" add column "force_sync" boolean not null default false; diff --git a/backend/supabase/migrations/20240624195400_stripe-optim.sql b/backend/supabase/migrations/20240624195400_stripe-optim.sql deleted file mode 100644 index e0a7286f2..000000000 --- a/backend/supabase/migrations/20240624195400_stripe-optim.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."user_settings" add column "last_stripe_check" timestamp with time zone; diff --git a/backend/supabase/migrations/20240711141940_notification_bulk.sql b/backend/supabase/migrations/20240711141940_notification_bulk.sql deleted file mode 100644 index 905106ab1..000000000 --- a/backend/supabase/migrations/20240711141940_notification_bulk.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."notifications" add column "bulk_id" uuid; - - diff --git a/backend/supabase/migrations/20240711150140_knowledge-process-status.sql b/backend/supabase/migrations/20240711150140_knowledge-process-status.sql deleted file mode 100644 index 36c318e09..000000000 --- a/backend/supabase/migrations/20240711150140_knowledge-process-status.sql +++ /dev/null @@ -1,7 +0,0 @@ -alter table "public"."knowledge" add column "status" text not null default 'UPLOADED'::text; - -CREATE UNIQUE INDEX knowledge_pkey ON public.knowledge USING btree (id); - -alter table "public"."knowledge" add constraint "knowledge_pkey" PRIMARY KEY using index "knowledge_pkey"; - - diff --git a/backend/supabase/migrations/20240712135540_notification_brain_category.sql b/backend/supabase/migrations/20240712135540_notification_brain_category.sql deleted file mode 100644 index f70dbc2f1..000000000 --- a/backend/supabase/migrations/20240712135540_notification_brain_category.sql +++ /dev/null @@ -1,5 +0,0 @@ -alter table "public"."notifications" add column "brain_id" uuid; - -alter table "public"."notifications" add column "category" text; - - diff --git a/backend/supabase/migrations/20240722102438_azure_additional_data.sql b/backend/supabase/migrations/20240722102438_azure_additional_data.sql deleted file mode 100644 index 4623878af..000000000 --- a/backend/supabase/migrations/20240722102438_azure_additional_data.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."syncs_user" add column "additional_data" jsonb default '{}'::jsonb; - - diff --git a/backend/supabase/migrations/20240722164034_onboarding-removed.sql b/backend/supabase/migrations/20240722164034_onboarding-removed.sql deleted file mode 100644 index 864c04762..000000000 --- a/backend/supabase/migrations/20240722164034_onboarding-removed.sql +++ /dev/null @@ -1,53 +0,0 @@ -drop policy "NOTIFICATIONS" on "public"."onboardings"; - -revoke delete on table "public"."onboardings" from "anon"; - -revoke insert on table "public"."onboardings" from "anon"; - -revoke references on table "public"."onboardings" from "anon"; - -revoke select on table "public"."onboardings" from "anon"; - -revoke trigger on table "public"."onboardings" from "anon"; - -revoke truncate on table "public"."onboardings" from "anon"; - -revoke update on table "public"."onboardings" from "anon"; - -revoke delete on table "public"."onboardings" from "authenticated"; - -revoke insert on table "public"."onboardings" from "authenticated"; - -revoke references on table "public"."onboardings" from "authenticated"; - -revoke select on table "public"."onboardings" from "authenticated"; - -revoke trigger on table "public"."onboardings" from "authenticated"; - -revoke truncate on table "public"."onboardings" from "authenticated"; - -revoke update on table "public"."onboardings" from "authenticated"; - -revoke delete on table "public"."onboardings" from "service_role"; - -revoke insert on table "public"."onboardings" from "service_role"; - -revoke references on table "public"."onboardings" from "service_role"; - -revoke select on table "public"."onboardings" from "service_role"; - -revoke trigger on table "public"."onboardings" from "service_role"; - -revoke truncate on table "public"."onboardings" from "service_role"; - -revoke update on table "public"."onboardings" from "service_role"; - -alter table "public"."onboardings" drop constraint "onboardings_user_id_fkey"; - -alter table "public"."onboardings" drop constraint "onboardings_pkey"; - -drop index if exists "public"."onboardings_pkey"; - -drop table "public"."onboardings" CASCADE; - -drop function if exists public.create_user_onboarding cascade; \ No newline at end of file diff --git a/backend/supabase/migrations/20240726142945_notifications-sync-update.sql b/backend/supabase/migrations/20240726142945_notifications-sync-update.sql deleted file mode 100644 index c3c49eca3..000000000 --- a/backend/supabase/migrations/20240726142945_notifications-sync-update.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."syncs_active" add column "notification_id" uuid; - - diff --git a/backend/supabase/migrations/20240806153621_model-description.sql b/backend/supabase/migrations/20240806153621_model-description.sql deleted file mode 100644 index 3d1681005..000000000 --- a/backend/supabase/migrations/20240806153621_model-description.sql +++ /dev/null @@ -1,5 +0,0 @@ -alter table "public"."models" add column "description" text not null default 'Default Description'::text; - -alter table "public"."models" add column "display_name" text not null default gen_random_uuid(); - -alter table "public"."models" add column "image_url" text not null default 'https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png'::text; diff --git a/backend/supabase/migrations/20240808161957_rag-models.sql b/backend/supabase/migrations/20240808161957_rag-models.sql deleted file mode 100644 index a3ec284cf..000000000 --- a/backend/supabase/migrations/20240808161957_rag-models.sql +++ /dev/null @@ -1,9 +0,0 @@ -alter table "public"."models" add column "default" boolean not null default false; - -alter table "public"."models" add column "endpoint_url" text not null default 'https://api.openai.com/v1/models'::text; - -alter table "public"."models" add column "env_variable_name" text not null default 'OPENAI_API_KEY'::text; - -alter table "public"."user_settings" drop column "models"; - - diff --git a/backend/supabase/migrations/20240808222754_new-default-value.sql b/backend/supabase/migrations/20240808222754_new-default-value.sql deleted file mode 100644 index 56cbb20c2..000000000 --- a/backend/supabase/migrations/20240808222754_new-default-value.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."models" alter column "endpoint_url" set default 'https://api.openai.com/v1'::text; diff --git a/backend/supabase/migrations/20240820091634_brain_snippet.sql b/backend/supabase/migrations/20240820091634_brain_snippet.sql deleted file mode 100644 index 6b9c17e91..000000000 --- a/backend/supabase/migrations/20240820091634_brain_snippet.sql +++ /dev/null @@ -1,5 +0,0 @@ -alter table "public"."brains" add column "snippet_color" text not null default '#d0c6f2'::text; - -alter table "public"."brains" add column "snippet_emoji" text not null default '🧠'::text; - - diff --git a/backend/supabase/migrations/20240820091834_notion-sync.sql b/backend/supabase/migrations/20240820091834_notion-sync.sql deleted file mode 100644 index 142d4f22c..000000000 --- a/backend/supabase/migrations/20240820091834_notion-sync.sql +++ /dev/null @@ -1,49 +0,0 @@ -create table "public"."notion_sync" ( - "id" UUID DEFAULT uuid_generate_v4() not null, - "notion_id" uuid not null, - "parent_id" uuid, - "is_folder" boolean, - "icon" text, - "last_modified" timestamp with time zone, - "web_view_link" text, - "type" text, - "name" text, - "mime_type" text, - "user_id" text -); -alter table "public"."notion_sync" enable row level security; -alter table "public"."syncs_active" -add column if not exists "notification_id" uuid; - - - - -CREATE UNIQUE INDEX notion_sync_pkey ON public.notion_sync USING btree (id, notion_id); - -alter table "public"."notion_sync" -add constraint "notion_sync_pkey" PRIMARY KEY using index "notion_sync_pkey"; -grant delete on table "public"."notion_sync" to "anon"; -grant insert on table "public"."notion_sync" to "anon"; -grant references on table "public"."notion_sync" to "anon"; -grant select on table "public"."notion_sync" to "anon"; -grant trigger on table "public"."notion_sync" to "anon"; -grant truncate on table "public"."notion_sync" to "anon"; -grant update on table "public"."notion_sync" to "anon"; -grant delete on table "public"."notion_sync" to "authenticated"; -grant insert on table "public"."notion_sync" to "authenticated"; -grant references on table "public"."notion_sync" to "authenticated"; -grant select on table "public"."notion_sync" to "authenticated"; -grant trigger on table "public"."notion_sync" to "authenticated"; -grant truncate on table "public"."notion_sync" to "authenticated"; -grant update on table "public"."notion_sync" to "authenticated"; -grant delete on table "public"."notion_sync" to "service_role"; -grant insert on table "public"."notion_sync" to "service_role"; -grant references on table "public"."notion_sync" to "service_role"; -grant select on table "public"."notion_sync" to "service_role"; -grant trigger on table "public"."notion_sync" to "service_role"; -grant truncate on table "public"."notion_sync" to "service_role"; -grant update on table "public"."notion_sync" to "service_role"; - -CREATE UNIQUE INDEX notion_sync_notion_id_key ON public.notion_sync USING btree (notion_id); - -alter table "public"."notion_sync" add constraint "notion_sync_notion_id_key" UNIQUE using index "notion_sync_notion_id_key"; diff --git a/backend/supabase/migrations/20240821191057_knowledge_fix.sql b/backend/supabase/migrations/20240821191057_knowledge_fix.sql deleted file mode 100644 index 391fc843d..000000000 --- a/backend/supabase/migrations/20240821191057_knowledge_fix.sql +++ /dev/null @@ -1,17 +0,0 @@ -ALTER TABLE "public"."knowledge" - RENAME COLUMN "integration" TO "source"; -ALTER TABLE "public"."knowledge" - RENAME COLUMN "integration_link" TO "source_link"; -ALTER TABLE "public"."knowledge" -add column "file_sha1" text; -ALTER TABLE "public"."knowledge" -add CONSTRAINT "unique_file_sha1" unique ("file_sha1"); -alter table "public"."knowledge" -add column "created_at" timestamp with time zone default now(); -alter table "public"."knowledge" -add column "file_size" bigint; -alter table "public"."knowledge" -add column "metadata" jsonb; -alter table "public"."knowledge" -add column "updated_at" timestamp with time zone default now(); -CREATE INDEX knowledge_file_sha1_hash_idx ON knowledge USING hash (file_sha1); diff --git a/backend/supabase/migrations/20240821201741_brain-knowledge.sql b/backend/supabase/migrations/20240821201741_brain-knowledge.sql deleted file mode 100644 index c839756d9..000000000 --- a/backend/supabase/migrations/20240821201741_brain-knowledge.sql +++ /dev/null @@ -1,101 +0,0 @@ -drop index if exists "public"."vectors_file_sha1_idx"; - -alter table "public"."vectors" drop column "file_sha1"; - -alter table "public"."vectors" add column "knowledge_id" uuid; - -alter table "public"."vectors" alter column "id" set default gen_random_uuid(); - -UPDATE "public"."vectors" -SET knowledge_id = knowledge.id -FROM knowledge, - brains_vectors -WHERE vectors.metadata->>'file_name' = knowledge.file_name -AND brains_vectors.vector_id = vectors.id -AND knowledge.brain_id = brains_vectors.brain_id; - - -alter table "public"."vectors" add constraint "public_vectors_knowledge_id_fkey" FOREIGN KEY (knowledge_id) REFERENCES knowledge(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."vectors" validate constraint "public_vectors_knowledge_id_fkey"; - - --- Creating BrainKnowledge - -CREATE INDEX vectors_knowledge_id_idx ON public.vectors USING btree (knowledge_id); - -create table "public"."knowledge_brain" ( - "id" bigint generated by default as identity not null, - "created_at" timestamp with time zone not null default now(), - "knowledge_id" uuid, - "brain_id" uuid -); - --- Backfill from existing knowledge -INSERT INTO "public"."knowledge_brain" (knowledge_id, brain_id) -SELECT k.id, k.brain_id -FROM knowledge k; - - -CREATE INDEX knowledge_brain_brain_id_idx ON public.knowledge_brain USING btree (brain_id); - -CREATE INDEX knowledge_brain_knowledge_id_idx ON public.knowledge_brain USING btree (knowledge_id); - -CREATE UNIQUE INDEX knowledge_brain_pkey ON public.knowledge_brain USING btree (id); - -alter table "public"."knowledge" drop column "brain_id"; - -alter table "public"."knowledge_brain" enable row level security; - -alter table "public"."knowledge_brain" add constraint "knowledge_brain_pkey" PRIMARY KEY using index "knowledge_brain_pkey"; - -alter table "public"."knowledge_brain" add constraint "public_knowledge_brain_brain_id_fkey" FOREIGN KEY (brain_id) REFERENCES brains(brain_id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."knowledge_brain" validate constraint "public_knowledge_brain_brain_id_fkey"; - -alter table "public"."knowledge_brain" add constraint "public_knowledge_brain_knowledge_id_fkey" FOREIGN KEY (knowledge_id) REFERENCES knowledge(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."knowledge_brain" validate constraint "public_knowledge_brain_knowledge_id_fkey"; - - -grant delete on table "public"."knowledge_brain" to "anon"; - -grant insert on table "public"."knowledge_brain" to "anon"; - -grant references on table "public"."knowledge_brain" to "anon"; - -grant select on table "public"."knowledge_brain" to "anon"; - -grant trigger on table "public"."knowledge_brain" to "anon"; - -grant truncate on table "public"."knowledge_brain" to "anon"; - -grant update on table "public"."knowledge_brain" to "anon"; - -grant delete on table "public"."knowledge_brain" to "authenticated"; - -grant insert on table "public"."knowledge_brain" to "authenticated"; - -grant references on table "public"."knowledge_brain" to "authenticated"; - -grant select on table "public"."knowledge_brain" to "authenticated"; - -grant trigger on table "public"."knowledge_brain" to "authenticated"; - -grant truncate on table "public"."knowledge_brain" to "authenticated"; - -grant update on table "public"."knowledge_brain" to "authenticated"; - -grant delete on table "public"."knowledge_brain" to "service_role"; - -grant insert on table "public"."knowledge_brain" to "service_role"; - -grant references on table "public"."knowledge_brain" to "service_role"; - -grant select on table "public"."knowledge_brain" to "service_role"; - -grant trigger on table "public"."knowledge_brain" to "service_role"; - -grant truncate on table "public"."knowledge_brain" to "service_role"; - -grant update on table "public"."knowledge_brain" to "service_role"; diff --git a/backend/supabase/migrations/20240902201241_knowledge-user.sql b/backend/supabase/migrations/20240902201241_knowledge-user.sql deleted file mode 100644 index 6b239d6d6..000000000 --- a/backend/supabase/migrations/20240902201241_knowledge-user.sql +++ /dev/null @@ -1,10 +0,0 @@ -alter table "public"."knowledge" add column "user_id" uuid; -alter table "public"."knowledge" add constraint "public_knowledge_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; -alter table "public"."knowledge" validate constraint "public_knowledge_user_id_fkey"; - --- alter table -ALTER TABLE "public"."knowledge" -DROP CONSTRAINT "unique_file_sha1"; - -ALTER TABLE "public"."knowledge" -ADD CONSTRAINT "unique_file_sha1_user_id" UNIQUE ("file_sha1", "user_id"); diff --git a/backend/supabase/migrations/20240905153004_knowledge-folders.sql b/backend/supabase/migrations/20240905153004_knowledge-folders.sql deleted file mode 100644 index 5b2ac3165..000000000 --- a/backend/supabase/migrations/20240905153004_knowledge-folders.sql +++ /dev/null @@ -1,31 +0,0 @@ -ALTER USER postgres -SET idle_session_timeout = '3min'; -ALTER USER postgres -SET idle_in_transaction_session_timeout = '3min'; --- Drop previous contraint -alter table "public"."knowledge" drop constraint "unique_file_sha1_user_id"; -alter table "public"."knowledge" -add column "is_folder" boolean default false; --- Update the knowledge to backfill knowledge to is_folder = false -UPDATE "public"."knowledge" -SET is_folder = false; --- Add parent_id -> folder -alter table "public"."knowledge" -add column "parent_id" uuid; -alter table "public"."knowledge" -add constraint "public_knowledge_parent_id_fkey" FOREIGN KEY (parent_id) REFERENCES knowledge(id) ON DELETE CASCADE; --- Add constraint must be folder for parent_id -CREATE FUNCTION is_parent_folder(folder_id uuid) RETURNS boolean AS $$ BEGIN RETURN ( - SELECT k.is_folder - FROM public.knowledge k - WHERE k.id = folder_id -); -END; -$$ LANGUAGE plpgsql; -ALTER TABLE public.knowledge -ADD CONSTRAINT check_parent_is_folder CHECK ( - parent_id IS NULL - OR is_parent_folder(parent_id) - ); --- Index on parent_id -CREATE INDEX knowledge_parent_id_idx ON public.knowledge USING btree (parent_id); diff --git a/backend/supabase/migrations/20240911145305_gtasks.sql b/backend/supabase/migrations/20240911145305_gtasks.sql deleted file mode 100644 index 2ea2e30b2..000000000 --- a/backend/supabase/migrations/20240911145305_gtasks.sql +++ /dev/null @@ -1,79 +0,0 @@ -create table "public"."tasks" ( - "id" bigint generated by default as identity not null, - "pretty_id" text, - "user_id" uuid not null default auth.uid(), - "status" text, - "creation_time" timestamp with time zone default (now() AT TIME ZONE 'utc'::text), - "answer_raw" jsonb, - "answer_pretty" text -); - - -alter table "public"."tasks" enable row level security; - -CREATE UNIQUE INDEX tasks_pkey ON public.tasks USING btree (id); - -alter table "public"."tasks" add constraint "tasks_pkey" PRIMARY KEY using index "tasks_pkey"; - -alter table "public"."tasks" add constraint "tasks_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; - -alter table "public"."tasks" validate constraint "tasks_user_id_fkey"; - -grant delete on table "public"."tasks" to "anon"; - -grant insert on table "public"."tasks" to "anon"; - -grant references on table "public"."tasks" to "anon"; - -grant select on table "public"."tasks" to "anon"; - -grant trigger on table "public"."tasks" to "anon"; - -grant truncate on table "public"."tasks" to "anon"; - -grant update on table "public"."tasks" to "anon"; - -grant delete on table "public"."tasks" to "authenticated"; - -grant insert on table "public"."tasks" to "authenticated"; - -grant references on table "public"."tasks" to "authenticated"; - -grant select on table "public"."tasks" to "authenticated"; - -grant trigger on table "public"."tasks" to "authenticated"; - -grant truncate on table "public"."tasks" to "authenticated"; - -grant update on table "public"."tasks" to "authenticated"; - -grant delete on table "public"."tasks" to "service_role"; - -grant insert on table "public"."tasks" to "service_role"; - -grant references on table "public"."tasks" to "service_role"; - -grant select on table "public"."tasks" to "service_role"; - -grant trigger on table "public"."tasks" to "service_role"; - -grant truncate on table "public"."tasks" to "service_role"; - -grant update on table "public"."tasks" to "service_role"; - -create policy "allow_user_all_tasks" -on "public"."tasks" -as permissive -for all -to public -using ((user_id = ( SELECT auth.uid() AS uid))); - - -create policy "tasks" -on "public"."tasks" -as permissive -for all -to service_role; - - - diff --git a/backend/supabase/migrations/20240918094405_assistants.sql b/backend/supabase/migrations/20240918094405_assistants.sql deleted file mode 100644 index 22b4c885f..000000000 --- a/backend/supabase/migrations/20240918094405_assistants.sql +++ /dev/null @@ -1,11 +0,0 @@ -alter table "public"."tasks" drop column "answer_pretty"; - -alter table "public"."tasks" drop column "answer_raw"; - -alter table "public"."tasks" add column "answer" text; - -alter table "public"."tasks" add column "assistant_id" bigint not null; - -alter table "public"."tasks" add column "settings" jsonb; - - diff --git a/backend/supabase/migrations/20240919100924_add_sync_status.sql b/backend/supabase/migrations/20240919100924_add_sync_status.sql deleted file mode 100644 index 84947f79e..000000000 --- a/backend/supabase/migrations/20240919100924_add_sync_status.sql +++ /dev/null @@ -1,9 +0,0 @@ -alter table "public"."notion_sync" add column "sync_user_id" bigint; - -alter table "public"."syncs_user" add column "status" text; - -alter table "public"."notion_sync" add constraint "public_notion_sync_syncs_user_id_fkey" FOREIGN KEY (sync_user_id) REFERENCES syncs_user(id) ON DELETE CASCADE not valid; - -alter table "public"."notion_sync" validate constraint "public_notion_sync_syncs_user_id_fkey"; - -alter publication supabase_realtime add table "public"."syncs_user" diff --git a/backend/supabase/migrations/20240919142842_policy_syncs.sql b/backend/supabase/migrations/20240919142842_policy_syncs.sql deleted file mode 100644 index 244c4f56d..000000000 --- a/backend/supabase/migrations/20240919142842_policy_syncs.sql +++ /dev/null @@ -1,9 +0,0 @@ -create policy "allow_user_all_syncs_user" -on "public"."syncs_user" -as permissive -for all -to public -using ((user_id = ( SELECT auth.uid() AS uid))); - - - diff --git a/backend/supabase/migrations/20240925083103_assistants-name.sql b/backend/supabase/migrations/20240925083103_assistants-name.sql deleted file mode 100644 index 79489e148..000000000 --- a/backend/supabase/migrations/20240925083103_assistants-name.sql +++ /dev/null @@ -1,9 +0,0 @@ -alter table "public"."tasks" add column "assistant_name" text; - -alter - publication supabase_realtime add table tasks; - - - - - diff --git a/backend/supabase/migrations/20240925124019_assistants-metadata.sql b/backend/supabase/migrations/20240925124019_assistants-metadata.sql deleted file mode 100644 index 782ab3d97..000000000 --- a/backend/supabase/migrations/20240925124019_assistants-metadata.sql +++ /dev/null @@ -1,4 +0,0 @@ - -alter table "public"."tasks" add column "task_metadata" jsonb; - - diff --git a/backend/supabase/migrations/20241008154800_check_file.sql b/backend/supabase/migrations/20241008154800_check_file.sql deleted file mode 100644 index cb47780fa..000000000 --- a/backend/supabase/migrations/20241008154800_check_file.sql +++ /dev/null @@ -1 +0,0 @@ -alter table "public"."knowledge" drop constraint if exists "knowledge_check"; diff --git a/backend/supabase/migrations/local_20240107152745_ollama.sql b/backend/supabase/migrations/local_20240107152745_ollama.sql deleted file mode 100644 index ded05c75f..000000000 --- a/backend/supabase/migrations/local_20240107152745_ollama.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table "public"."vectors" alter column "embedding" set not null; - -alter table "public"."vectors" alter column "embedding" set data type vector using "embedding"::vector; diff --git a/backend/supabase/schema.sql b/backend/supabase/schema.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/supabase/seed.sql b/backend/supabase/seed.sql deleted file mode 100644 index b15761bf6..000000000 --- a/backend/supabase/seed.sql +++ /dev/null @@ -1,358 +0,0 @@ -SET session_replication_role = replica; - --- --- PostgreSQL database dump --- - --- Dumped from database version 15.1 (Ubuntu 15.1-1.pgdg20.04+1) --- Dumped by pg_dump version 15.7 (Ubuntu 15.7-1.pgdg20.04+1) - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - --- --- Data for Name: audit_log_entries; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - -INSERT INTO "auth"."audit_log_entries" ("instance_id", "id", "payload", "created_at", "ip_address") VALUES - ('00000000-0000-0000-0000-000000000000', '84e89c28-6f5f-4e24-a03b-68cdaa90b3f2', '{"action":"user_signedup","actor_id":"00000000-0000-0000-0000-000000000000","actor_username":"service_role","actor_via_sso":false,"log_type":"team","traits":{"user_email":"admin@quivr.app","user_id":"39418e3b-0258-4452-af60-7acfcc1263ff","user_phone":""}}', '2024-01-22 22:27:00.164777+00', ''), - ('00000000-0000-0000-0000-000000000000', 'ac1d43e6-2b2a-4af1-bdd1-c03907e7ba5a', '{"action":"login","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-01-22 22:27:50.16388+00', ''), - ('00000000-0000-0000-0000-000000000000', 'e86de23b-ea26-408e-8e8c-a97d2f1f259c', '{"action":"login","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-02-06 04:08:08.378325+00', ''), - ('00000000-0000-0000-0000-000000000000', 'a356df58-6d1b-45dd-ae13-08ea7b7a0aff', '{"action":"login","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-02-21 02:17:19.558786+00', ''), - ('00000000-0000-0000-0000-000000000000', '8a1ff5f6-5426-4f4b-94ac-d780f6308a8d', '{"action":"logout","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account"}', '2024-03-05 16:11:51.53268+00', ''), - ('00000000-0000-0000-0000-000000000000', '798fde42-d617-4dfd-bf0c-4a6e15dbadbf', '{"action":"login","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-03-05 16:12:06.445094+00', ''), - ('00000000-0000-0000-0000-000000000000', '73331e6e-2e66-4db6-816b-980ce65a2481', '{"action":"user_recovery_requested","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"user"}', '2024-03-05 16:22:13.777822+00', ''), - ('00000000-0000-0000-0000-000000000000', 'dcdc846a-7194-4ee4-869e-0c05c269bd75', '{"action":"login","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-03-30 23:21:12.07649+00', ''), - ('00000000-0000-0000-0000-000000000000', '3cb1aa8a-bbcf-4871-b9f1-ae3c8a1e7897', '{"action":"token_refreshed","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-03-31 00:39:12.712906+00', ''), - ('00000000-0000-0000-0000-000000000000', 'b742a419-21c6-4a89-b966-5fcb44d74ce6', '{"action":"token_revoked","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-03-31 00:39:12.714476+00', ''), - ('00000000-0000-0000-0000-000000000000', '7f37fc2f-4122-4b7d-9be5-8e0e5c217d1b', '{"action":"token_refreshed","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-03-31 00:39:12.750952+00', ''), - ('00000000-0000-0000-0000-000000000000', '3a20a51b-0b9b-4fd2-88bb-baf0bd0256e7', '{"action":"token_revoked","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-03-31 00:39:12.751734+00', ''), - ('00000000-0000-0000-0000-000000000000', '7abd9db8-feaa-4286-91ce-f83f26e6248f', '{"action":"token_refreshed","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 17:40:15.324815+00', ''), - ('00000000-0000-0000-0000-000000000000', '60cbbae4-8f7d-424a-bff9-52f36a3cec18', '{"action":"token_revoked","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 17:40:15.327436+00', ''), - ('00000000-0000-0000-0000-000000000000', '8fa8d8cc-069a-4937-9381-f4e0c2014cee', '{"action":"token_refreshed","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 17:40:29.606995+00', ''), - ('00000000-0000-0000-0000-000000000000', 'c6b4b0f9-6f7c-4e99-b615-19dcc93344bc', '{"action":"user_signedup","actor_id":"00000000-0000-0000-0000-000000000000","actor_username":"service_role","actor_via_sso":false,"log_type":"team","traits":{"user_email":"stan@quivr.app","user_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","user_phone":""}}', '2024-04-01 18:33:18.58257+00', ''), - ('00000000-0000-0000-0000-000000000000', '9996a09d-f340-4d8a-8121-1c61678a0035', '{"action":"logout","actor_id":"39418e3b-0258-4452-af60-7acfcc1263ff","actor_username":"admin@quivr.app","actor_via_sso":false,"log_type":"account"}', '2024-04-01 18:33:27.294395+00', ''), - ('00000000-0000-0000-0000-000000000000', 'a9e1b230-fcff-4bb7-97ee-99268a8004e5', '{"action":"login","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-04-01 18:33:48.194838+00', ''), - ('00000000-0000-0000-0000-000000000000', '227ba305-48e0-4b35-bdf3-1e85dec2b5ff', '{"action":"token_refreshed","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 22:57:02.635248+00', ''), - ('00000000-0000-0000-0000-000000000000', '42778009-b9f3-4f5a-b7d9-969180d2e018', '{"action":"token_revoked","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 22:57:02.638784+00', ''), - ('00000000-0000-0000-0000-000000000000', '777a94af-6b4f-4f25-b52b-7e8df2986f9b', '{"action":"token_refreshed","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-01 22:57:03.019661+00', ''), - ('00000000-0000-0000-0000-000000000000', '79ba8a24-2851-4027-97ae-257779dd3730', '{"action":"token_refreshed","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 00:29:49.500124+00', ''), - ('00000000-0000-0000-0000-000000000000', '0a936f39-4621-41b9-840a-29d8910a3a01', '{"action":"token_revoked","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 00:29:49.507212+00', ''), - ('00000000-0000-0000-0000-000000000000', '4b211d7b-3204-409a-9d01-89912527445f', '{"action":"token_refreshed","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 00:29:49.532939+00', ''), - ('00000000-0000-0000-0000-000000000000', 'bdf19c9f-51ee-4068-8b1d-844c99409c53', '{"action":"token_revoked","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 00:29:49.533754+00', ''), - ('00000000-0000-0000-0000-000000000000', '381c44bf-c4b1-4a1c-ac1d-b17b8881c703', '{"action":"token_refreshed","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 01:28:09.342118+00', ''), - ('00000000-0000-0000-0000-000000000000', 'f376cacc-43f2-440d-a048-c19d1aa6373b', '{"action":"token_revoked","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"token"}', '2024-04-02 01:28:09.345574+00', ''), - ('00000000-0000-0000-0000-000000000000', '7bc1f624-8cd0-4fab-8202-540e5e5a43c6', '{"action":"logout","actor_id":"39a23896-40b9-45cb-8a2c-6223c48e4b35","actor_username":"stan@quivr.app","actor_via_sso":false,"log_type":"account"}', '2024-04-02 01:34:52.219229+00', ''); - - --- --- Data for Name: flow_state; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - -INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", "phone_change_token", "phone_change_sent_at", "email_change_token_current", "email_change_confirm_status", "banned_until", "reauthentication_token", "reauthentication_sent_at", "is_sso_user", "deleted_at") VALUES - ('00000000-0000-0000-0000-000000000000', '39418e3b-0258-4452-af60-7acfcc1263ff', 'authenticated', 'authenticated', 'admin@quivr.app', '$2a$10$vwKX0eMLlrOZvxQEA3Vl4e5V4/hOuxPjGYn9QK1yqeaZxa.42Uhze', '2024-01-22 22:27:00.166861+00', NULL, '', NULL, 'e91d41043ca2c83c3be5a6ee7a4abc8a4f4fa2afc0a8453c502af931', '2024-03-05 16:22:13.780421+00', '', '', NULL, '2024-03-30 23:21:12.077887+00', '{"provider": "email", "providers": ["email"]}', '{}', NULL, '2024-01-22 22:27:00.158026+00', '2024-04-01 17:40:15.332205+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL); - - --- --- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - -INSERT INTO "auth"."identities" ("provider_id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", "updated_at", "id") VALUES - ('39418e3b-0258-4452-af60-7acfcc1263ff', '39418e3b-0258-4452-af60-7acfcc1263ff', '{"sub": "39418e3b-0258-4452-af60-7acfcc1263ff", "email": "admin@quivr.app", "email_verified": false, "phone_verified": false}', 'email', '2024-01-22 22:27:00.163787+00', '2024-01-22 22:27:00.163855+00', '2024-01-22 22:27:00.163855+00', '35f91d2f-db60-474c-8dd2-3fcbed9869bd'); - - --- --- Data for Name: instances; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: sessions; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: mfa_amr_claims; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: mfa_factors; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: mfa_challenges; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: one_time_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: sso_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: saml_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: saml_relay_states; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: sso_domains; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - - --- --- Data for Name: prompts; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: brains; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."brains" ("brain_id", "name", "status", "model", "max_tokens", "temperature", "description", "prompt_id", "last_update", "brain_type", "openai_api_key", "meaning", "tags") VALUES - ('40ba47d7-51b2-4b2a-9247-89e29619efb0', 'Default brain', 'private', 'gpt-3.5-turbo-0125', 2000, NULL, 'This is a description', NULL, '2024-08-08 22:23:45.136355', 'doc', NULL, '[-0.0032904116,0.023381991,-0.0049651316,-0.0075893654,0.014517613,0.010739796,-0.022558967,0.00081501354,-0.019860527,-0.018268446,-0.0068506673,0.021681974,-0.006955232,0.011947348,-0.016487475,0.013208869,0.0160962,0.0043647285,0.0034978543,0.007474682,0.004607588,-0.0032448755,0.01571842,0.01895655,-0.003986947,-0.006695507,0.011198531,-0.009950502,0.010483444,0.000952887,0.031032072,0.017850189,0.007110392,-0.019806558,0.03443211,0.009120731,-0.003932978,0.0034017223,0.026093926,-0.0028822725,0.018713688,0.008972317,0.008952078,-0.0061996686,-0.0061052227,-0.018686704,-0.009033032,-0.015758896,-0.010706065,0.0046885414,0.00877668,0.028954273,-0.012379099,0.022167694,-0.009255653,-0.0020153981,-0.010436221,0.013829511,0.04055757,-0.00810207,0.0056262496,-0.021641498,-0.024029618,-0.011225515,-0.0063582016,-0.014719997,0.002300421,0.017823203,-0.008796918,-0.008891364,0.026728058,-0.014895394,0.0054980735,-0.004715526,0.017998602,0.0013593397,-0.013283077,0.011427898,-0.0010270941,0.0034573777,0.016973194,-0.016258107,-0.03157176,0.02091292,0.012439813,-0.004793106,-0.007940163,0.012466798,-0.014099355,-0.0127838645,-0.0075151585,-0.0043107597,0.004658184,0.011367183,-0.009147716,0.025055025,-0.0031234457,0.019158931,0.013202123,-0.031733666,0.026134402,-0.0093096215,-0.01620414,-0.020440692,-0.017647805,-0.009188192,0.0028299901,0.008702473,0.010854479,-0.0073937285,-0.0010557651,0.005693711,-0.013816019,-0.029655868,0.0058927205,0.00049710344,0.019766081,-0.0015389547,-0.0005287258,-0.016298585,0.025108995,0.021749435,0.0114481365,-0.023112148,-0.0050899344,0.023854218,-0.010388998,-0.013202123,-0.0033055902,0.003050925,0.01987402,-0.002560146,0.010537412,0.00081501354,-0.016379537,0.022626428,-0.023867711,0.016662873,-0.028063787,0.011279483,0.0057881563,0.030573338,-0.0144636445,0.0024606409,-0.0014546284,0.023152625,-0.008513582,0.013923956,0.015273176,-0.010125901,0.0003971768,0.008992555,0.033190824,-0.008041355,-0.0030745363,0.0031386244,0.008473105,0.03834485,-0.005592519,-0.007562381,0.0024420891,-0.0001785609,-0.006759595,-0.029709836,0.05040688,0.038938504,0.00893184,-0.018093048,0.018403368,-0.036294032,0.0034506316,0.02594551,-0.04190679,0.01774225,-0.013600143,0.020022433,0.031922556,-0.018646227,-0.01379578,-0.018295432,-0.035079733,-0.01987402,-0.009626688,0.0025449672,0.0027574694,-0.025338361,0.012952517,-0.038075004,0.013910464,0.011191784,0.014207292,0.020697042,-0.0022818693,0.0058792285,-0.67655313,-0.022734366,-0.015286669,-0.014625551,0.0070969,0.028360616,-0.0032566811,-0.00669888,0.009087,0.008250483,-0.007940163,-0.0015372682,-0.0062030414,0.0046278266,-0.01388348,-0.026755044,0.0027608424,0.014153323,-0.006381813,0.012493783,-0.009060016,0.02317961,-0.0121092545,-0.006624673,-0.019482745,0.018200986,0.012136239,-0.01615017,-0.0038284133,0.0140184015,-0.040962335,0.004044289,0.008601281,-0.014585074,0.04182584,0.010308045,-0.021614512,0.014166816,0.0003436296,0.0023408977,-0.025608206,-0.0027068737,0.001866984,-0.02655266,-0.028009819,0.017458914,0.009410813,0.00972788,-0.0099842325,-0.022680398,0.022788335,0.0077107954,0.0026933816,0.0047627487,0.007731034,-0.0012581481,0.039694067,-0.008243738,0.0077175414,-0.0048808055,-0.005855617,0.037103564,-0.015097778,-0.0052383486,-0.0045704846,0.013391014,-0.009066762,-0.0041657183,-0.0019175797,-0.04330998,0.020467676,0.007967147,0.017175578,0.0022565713,0.025581222,-0.0005249311,0.022073248,-0.00017676897,-0.0005544453,0.023408977,-0.0015145,0.00036428956,-0.0039026204,-0.036321018,0.011731473,-0.014652535,-0.006837175,-0.0078457175,0.004479412,0.0015414845,-0.00057679176,-0.0052720793,-0.010044947,-0.0041252417,-0.001479083,0.0009073508,0.0015052242,0.0032347562,0.025959004,0.011765203,0.00022936749,-0.0058758557,-0.007953655,0.027686005,0.0064155436,0.009822326,-0.027578067,0.039424226,0.04606239,-0.0064796316,-0.004607588,0.004462547,-0.008999301,0.00749492,0.00055528857,-0.032759074,0.01878115,-0.022005787,0.026903458,0.0050191004,0.035430532,0.015610482,0.011292976,0.0017927767,0.047060814,-0.0010397431,-0.011900125,-0.006503243,-0.029332055,0.0042972676,0.019563697,-0.011873141,0.0059365705,-0.016190646,0.0053530326,0.028873319,0.01743193,0.0020322634,0.0062131607,-0.0009950502,-0.008985809,-0.0029918966,0.020116879,-0.008479851,-0.033865437,-0.020670058,-0.019253377,0.012284653,-0.010753288,-0.0010768466,-0.0057544257,-0.02411057,-0.01602874,0.011940602,0.024636768,-0.019860527,0.005909586,-0.011927109,-0.0054103746,-0.006904636,0.017270023,0.03016857,0.000106514635,-0.011002894,-0.025716143,0.020953394,-0.016285092,0.015704928,-0.0108747175,-0.044200465,-0.012068777,0.008385406,-0.013708081,-0.024447877,-0.0035821805,0.014490629,-0.011110831,-0.028792366,0.013869987,-0.004756002,0.004594096,0.03089715,-0.0008141703,0.0021671855,0.02776696,0.0070564235,0.04012582,0.007272299,-0.0032398158,-0.010078678,0.0014984781,0.029224116,-0.012392591,0.01367435,-0.017715266,-0.00032233723,-0.012129493,-0.006961978,-0.016473982,0.015178731,0.026620122,0.010962417,0.031652715,0.008513582,0.028252678,-0.036833722,0.034594014,-0.006445901,-0.0033865436,-0.0068236827,0.011259246,-0.015529528,0.012959263,0.0064627663,0.007751272,0.0011527403,0.012142985,-0.007562381,-0.021250224,-0.002826617,0.0030374327,-0.0056667263,0.005649861,0.0039734547,-0.0085203275,0.03775119,0.0111040855,0.022167694,0.008203261,-0.033433687,0.019914495,0.0071980916,0.025419315,-0.00067418866,0.002406672,0.015570005,0.00853382,-0.0072520603,0.022167694,0.008729457,0.029439993,0.03016857,0.013559666,-0.013755304,0.012662435,0.005511566,0.017917648,0.013208869,-0.017499391,-0.006503243,-0.015947787,-0.013546174,-0.021182762,0.01183941,-0.00789294,0.006307606,-0.01706764,0.011852902,0.0035990458,0.02276135,0.017162085,0.014976348,0.01774225,0.009761611,0.024920104,-0.018079555,-0.0023122267,0.012271161,0.018457336,-0.036132127,-0.020953394,0.012642196,-0.007299283,-0.029952696,0.0017674789,0.017202562,-0.009775103,0.0010026395,0.01052392,0.010388998,-0.028414585,-0.012028301,0.0042466717,0.0321924,-0.00069695676,-0.001726159,0.0029885236,0.008979063,-0.011346945,0.023328023,0.0018450591,0.013498952,-0.003959962,0.0028856455,-0.0068540405,0.007636588,0.0011932169,-0.0050696963,-0.012621958,-0.019307345,0.001559193,0.008358422,0.010186615,-0.012068777,0.005926451,0.01847083,-0.013350537,0.006115342,-0.0032111448,0.010442967,0.022612937,-0.004290521,0.008210007,-0.021466099,0.025311377,0.016446998,0.018862104,0.020562122,0.005265333,0.029655868,0.0056903376,-0.029790789,-0.028900305,0.012237431,0.021452608,0.013647365,-0.014058878,0.0149223795,-0.00041130144,-0.013923956,-0.046844937,0.0052147373,0.020481167,-0.023166116,-0.016797796,0.0075151585,0.0064931237,0.027793944,0.020211324,-0.011819172,-0.0045907227,0.011097339,-0.012581482,-0.008196515,-0.0036395225,-0.004452428,-0.013127916,0.010712811,0.009633435,-0.0171351,-0.0020676805,0.007285791,-0.0012826028,-0.010469952,0.014949364,0.021857373,0.017391453,-0.007602858,-0.008466359,0.020103386,-0.011954094,0.018848611,-0.0162716,0.01706764,0.006685388,0.024339939,0.010409237,-0.017458914,0.032893997,-0.009167953,-0.005585773,-0.019604174,-0.007596112,-0.024312954,0.010193361,0.0014411362,-0.021614512,-0.018943056,0.024987565,0.009006047,0.00082471105,0.022059755,-0.014369199,-0.026835997,-0.028657444,-0.020656567,-0.0045131426,0.009012793,-0.01040249,-0.021789912,0.0018366264,0.0041589723,-0.016366046,0.0008879558,-0.021924835,0.008216753,-0.01144139,0.002367882,-0.0058589904,-0.009343352,0.010011217,-0.026039956,-0.0071238843,0.0033190825,-0.005771291,-0.021371653,0.0005776351,-0.011333453,0.018929563,-0.0065336004,0.01663589,-0.0029328682,0.009444544,0.010928687,0.010442967,0.028414585,0.030843182,0.009478275,0.013417998,0.013579905,0.023557391,0.0011704488,-0.004749256,-0.025837574,-0.0060343887,-0.028711414,-0.0088508865,0.0026124283,0.015880326,-0.00881041,0.014396183,0.018174,0.0004869843,0.0013534368,0.0044288165,-0.010240584,0.009802087,-0.0073330137,0.01621763,0.0032549945,0.02489312,-0.0010591381,0.0071980916,-0.009093747,0.021385146,-0.031922556,-0.0040948843,0.03829088,-0.0011392481,0.00872271,0.007838971,-0.03402734,-0.016001755,-0.0017286888,0.009896533,-0.01395094,0.0017691654,-0.03216542,-0.002759156,-0.02000894,-0.016190646,0.014261262,-0.0030104483,-0.0025871303,-0.034998782,0.03181462,-0.028225694,-0.012311637,-0.014612058,-0.027928865,-0.0044558006,-0.0051742606,-0.0037778176,0.037238486,-0.017526375,0.016649382,-0.0013551234,0.013492205,0.011239007,-0.0005755269,-0.01621763,-0.011900125,0.03259717,0.03248923,0.023152625,0.022639921,0.0058589904,0.0115628205,0.007481428,-0.0042230603,-0.0003689275,-0.04044963,-0.016595412,0.006263756,0.009653673,0.006756222,0.02501455,0.016649382,0.0033190825,0.0034017223,0.014571582,0.011508851,-0.017377961,-0.028009819,-0.0003155911,0.007090154,-0.0050865617,0.0021418876,-0.034594014,-0.010483444,0.020885933,0.0041555995,0.015408099,0.013249346,-0.0014900455,-0.03162573,0.010564397,0.03418925,0.015704928,-0.023314532,-0.012419575,-0.009802087,-0.0050224736,0.0091949385,0.012696166,0.02293675,-0.0015895504,-0.018916072,-0.0065336004,-0.01914544,-0.014220784,0.007731034,-0.02710584,0.0019108336,-0.016892241,-0.020697042,-0.027982835,-0.022046264,0.021601021,-0.0043512364,0.0014057192,0.041582976,-0.020953394,-0.027443146,0.018632736,-0.0069889626,0.053078335,0.01571842,0.03993693,0.018484322,0.00078592094,0.001976608,0.0091949385,-0.016190646,-0.00020891837,0.036806736,0.0162716,0.0018147016,-0.01945576,0.008884617,0.022167694,-0.0041657183,-0.024731213,-0.0091949385,0.003403409,0.0052821985,-0.015920803,-0.009559227,-0.01144139,0.004735764,-0.0012606779,-0.02636377,-0.023516914,-0.006553839,-0.020777997,-0.0025179829,0.015435083,0.027618544,0.017229546,0.01379578,-0.012392591,0.01395094,-0.0233685,0.0064222896,0.029089196,0.007798495,0.0028215575,0.01045646,0.016136678,0.002293675,-0.0018197612,0.0040341695,0.009997725,0.03157176,-0.016554937,-0.0112052765,-0.014369199,-0.023422468,0.013816019,-0.0054171206,-0.0050427117,-0.013512444,-0.011340199,-0.004330998,0.0111648,-0.0009478274,-0.030546352,-0.021196255,-0.008284214,-0.0069282474,-0.02514947,-0.006580823,-0.003798056,-0.03489084,-0.0015566632,-0.015030317,0.02416454,0.009505259,0.016325569,-0.00530581,0.027618544,0.041933775,-0.007987386,0.0039228587,-0.023881204,-0.0035956728,-0.04374173,0.006047881,-0.003448945,-0.02508201,0.028225694,-0.035376564,-0.008054847,-0.012460052,0.0055520427,0.02037323,0.00021714019,-0.012183461,0.0005232446,0.004938147,0.02269389,0.014396183,-0.028252678,0.0077782567,0.000319175,-0.007623096,0.0058050216,0.004782987,-0.011137815,0.001495105,0.02293675,0.024636768,-0.030924136,-0.020980379,-0.010577889,0.029035226,0.004395086,0.0016435193,0.0010574516,-0.029655868,-0.022289123,0.017094625,0.0033443805,-0.021074824,-0.014517613,0.0036159111,-0.015435083,0.003491108,-0.011832664,-0.010537412,-0.022653412,-0.025702652,-0.06416893,-0.00239318,-0.027659021,0.023408977,0.03615911,-0.007596112,-0.006452647,-0.027132826,-0.027686005,0.008432629,-0.0104294745,0.01755336,0.021088317,-0.0046244534,0.01725653,0.013546174,0.0017809711,0.012385844,-0.0023240324,0.01957719,-0.0050966805,-0.014355707,-0.01559699,-0.0028434822,-0.0012328502,-0.0055689076,0.02715981,-0.009903279,-0.020467676,-0.0025753246,-0.013991417,-0.0026782027,-0.0123251295,0.013357284,-0.00637844,0.016001755,0.012507275,-0.0183494,-0.003378111,-0.0103282835,0.005390136,0.017148593,0.017809711,0.0107735265,-0.0072318222,0.0046952874,-0.025621697,0.014652535,0.0024926849,-0.0022481387,0.012439813,-0.021466099,0.001328139,-0.0012969383,0.016420014,0.00031116398,-0.0028620341,0.015704928,-0.009660419,-0.0077850027,-0.004148853,-0.027429653,-0.0034337663,0.0003261319,0.0027793944,-0.03872263,-0.017270023,-0.025567729,-0.0015617228,-0.02594551,0.0019192662,0.0149223795,-0.042986166,-0.025527252,-0.0045030233,-0.022734366,-0.00029851505,-0.019334331,0.0016848391,-0.016757319,-0.0073802364,0.009296129,-0.009916771,-0.019833542,0.015637467,0.009491767,0.007434205,0.016608905,0.20054814,-0.0066685225,-0.0028131248,0.03205748,0.005700457,0.022518491,0.00017824468,-0.0075353966,0.009026285,0.02000894,0.008641758,-0.031301916,-0.023220086,0.01036876,0.0033949763,-0.0034540046,-0.025729636,-0.02208674,-0.025824081,-0.051135458,0.013586651,-0.009545735,-0.021250224,-0.024245493,0.011610043,-0.010739796,-0.0045401272,0.00025276805,0.020966887,0.013195377,-0.0035922998,0.00040497698,0.014881902,-0.012176716,-0.038021035,-0.007548889,-0.0018197612,-0.0009410813,0.0055520427,0.0146930115,0.004334371,-0.00037230054,0.017040655,0.020481167,-0.001347534,0.030087618,0.021735944,-0.0058252597,0.0129997395,0.00032592108,-0.01348546,0.004735764,0.029035226,0.025419315,-0.0061760573,0.0018804761,0.022397062,0.0034573777,0.009815579,0.0041184956,-0.029628884,0.038695645,-0.011947348,0.037670236,-0.010442967,0.017243039,-0.013512444,-0.016608905,0.03653689,-0.006624673,0.0022127216,-0.023031196,-0.010078678,-0.0057814103,-0.0204272,-0.006715745,0.016123185,0.0040409155,0.019293854,0.030627307,-0.03267812,-0.018200986,-0.01744542,-0.012925533,-0.011603297,-0.0162716,0.010982655,-0.0010093856,-0.004847075,-0.011515598,-0.010179869,-0.004000439,0.005585773,-0.014436659,0.01367435,0.02776696,-0.008864379,0.021371653,-0.03572736,0.0047661215,-0.035781328,0.055075184,0.032920983,0.025527252,-0.011137815,-0.00034004575,0.0058522443,0.01890258,-0.0068540405,0.0060647465,-0.013910464,-0.0044321893,-0.009404067,-0.008236991,0.0025584595,0.016797796,-0.024178032,-0.0058218865,-0.0032094584,-0.012891802,0.0017523002,-0.017054148,-0.02104784,0.009761611,-0.018511306,-0.017216055,-0.011954094,-0.002524729,0.004064527,-0.028252678,0.015367622,0.013215615,-0.0121092545,-0.007946909,-0.02307167,0.0070631695,0.016595412,0.0058859745,-0.018929563,0.016986687,-0.011798934,-0.0054980735,0.013168393,-0.0153541295,-0.005207991,-0.028657444,-0.008641758,0.024812166,-0.011940602,-0.0016182214,-0.023827234,-0.018066064,-0.0046480647,-0.00785921,0.011407659,0.008540566,-0.014585074,-0.03451306,-0.014800949,-0.0062064147,-0.008985809,-0.0005004765,0.013364029,-0.007420713,-0.014031894,0.0026866354,-0.17464311,-0.008122307,0.030978104,-0.02288278,0.012338622,-0.019671636,-0.012196953,0.002948047,-0.0019513102,0.010476697,0.0064762584,-9.075616e-05,-0.0009006047,-0.015462068,-0.0003343537,-0.016811289,-0.021371653,0.0007728504,0.010537412,-0.0047796136,0.00095373025,-0.0031875335,0.008378659,0.0098695485,-0.013458475,0.0022498253,0.008075085,0.04819416,0.019604174,-0.010685827,-0.00789294,-0.023449453,0.028279662,0.0076635727,0.023341516,-0.0053361673,0.0043849666,0.0018872223,-0.02489312,0.007144123,0.0036496415,0.02790188,0.008479851,-0.0036395225,0.024420891,0.016757319,0.01847083,-0.029898727,0.009255653,-0.010395744,0.008554058,-0.030087618,-0.018254954,-0.00031285052,0.025284393,0.015246192,0.0087496955,-0.0031689818,0.0037508332,0.0021705586,-0.021385146,-0.027820928,-0.012089016,-0.008574297,-0.0020676805,-0.002325719,-0.013087439,0.028846335,0.008742949,0.009350099,-0.0070564235,-0.016069217,0.014679519,-0.009167953,0.015421591,0.002652905,-0.013465221,0.027389176,0.0089655705,0.004911163,-0.002656278,0.03016857,-0.016177155,-0.0103282835,-0.014450152,-0.0057611717,-0.0072183297,0.008817156,0.024205016,-0.015799373,0.004401832,-0.03443211,-0.014247769,-0.017081132,0.008297706,0.025176456,-0.004192703,0.00048529776,0.011023132,0.008075085,-0.0055419235,0.0122239385,-0.01744542,0.026215356,0.0073127756,-0.018686704,-0.0042601638,0.012062032,0.006813564,0.00085085223,0.0055621616,0.0026512183,0.0146930115,0.038938504,-0.03108604,-0.00682031,-0.0334067,-0.014031894,0.012250923,0.0053294213,0.03920835,-0.0054272395,-0.009613196,0.008581042,0.000869404,0.0073937285,-0.075718254,-0.045657624,0.016015248,0.015489052,-0.0024926849,0.005592519,-0.01652795,0.010449713,-0.017917648,0.006017524,-0.019914495,-0.0054879547,-0.026269324,-0.019050995,0.016163662,-0.02006291,0.008176276,0.02624234,-0.0075353966,0.024987565,-0.0065133623,-0.011171546,-0.0030340597,-0.032381292,0.0068101906,0.008790172,-0.024636768,0.023962157,0.027146317,-0.009997725,-0.0113199605,-0.04166393,0.007609604,-0.01670335,0.0065336004,-0.00023969746,0.0013812645,-0.0067832065,0.025648683,-0.0304654,-0.003936351,-0.015866833,0.014436659,-0.010517174,-0.014531105,0.013613636,-0.020643074,0.025554238,-0.017364468,-0.02305818,0.0026360396,-0.022855796,-0.0017556732,0.0009638494,0.021520067,-0.011421151,0.036401972,0.010948924,-0.001264051,0.003740714,0.013512444,0.0102136,-0.030276509,0.00550482,0.0221542,-0.012810849,-0.034567032,-0.0122037,0.012702911,-0.017580343,-0.018605752,0.014666027,0.002435343,0.012885056,-0.025999479,-0.025243916,-0.010267569,-0.01235886,0.006877652,0.004749256,-0.025675667,-0.0031723548,0.0052214833,-0.02863046,-0.017850189,0.015151747,0.0028114384,0.012891802,0.014112847,-0.04201473,0.008844141,0.030006666,-0.012925533,-0.020049417,-0.0144636445,-0.019118454,0.022370076,0.009140969,0.0023796877,-0.0055351774,-0.039262317,-0.017944634,-0.073181726,0.018025586,-0.0088508865,-0.020858949,0.0032904116,0.012466798,-0.012230684,-0.0032634272,-0.022208171,-0.0012573049,0.00450977,0.0023628224,-0.01547556,0.010220346,-0.023692314,-0.024151048,0.018686704,-0.032030497,0.014490629,0.015381115,0.016487475,-0.004671676,0.0024775062,-8.2218125e-05,-0.017688282,0.012176716,-0.013296569,0.014935872,-0.021520067,0.014895394,0.005579027,-0.00993701,0.0026765163,0.01015963,-0.00474251,-0.0119945705,0.018740673,0.026768535,-0.011313214,0.022059755,-0.022869289,-0.03437814,0.019361315,-0.025041534,0.0025820709,0.0048841783,-0.048005268,-0.021155778,0.0330559,0.0037575793,0.00810207,0.023530407,-0.020602597,-0.0036327762,-0.033676546,0.020143863,-0.0053091827,0.006617927,-0.01999545,-0.011272738,0.03958613,0.0068742787,0.0087496955,-0.003555196,-0.015381115,0.0021756182,-0.013323553,0.00829096,0.0077107954,-0.039909944,0.0031386244,0.007440951,-0.0073802364,0.02293675,0.027928865,-0.005312556,-0.004411951,0.008999301,-0.016905734,0.025270902,0.034216233,0.012460052,-0.026957426,0.010388998,0.022437537,0.0042432984,-0.017877173,0.010557651,-6.698669e-05,-0.005656607,-0.02771299,0.022046264,0.003082969,0.015016825,0.007946909,-0.00829096,-0.011758457,-0.0047054067,0.022504998,0.03977502,0.0063514556,0.015489052,4.0285563e-06,-0.0069282474,-0.010955671,-0.0033325749,-0.025338361,-0.0064863777,0.028927289,0.02104784,0.0018416861,-0.012014809,-0.017580343,0.01312117,-0.031787638,0.008695726,-0.012487036,-0.0077040493,-0.025689159,0.018335907,-0.012385844,0.00040181473,0.01113107,-0.022909764,-0.0015760582,0.008628265,-0.005525058,-0.031841606,0.008614773,-0.0011510538,0.026687583,0.011610043,-0.028954273,0.001973235,-0.008574297,-0.013411252,-0.014247769,0.019293854,-0.004644692,0.08176277,-0.016757319,-0.0108747175,-0.010220346,0.0008280841,-0.0003354078,-0.01853829,0.015758896,-0.015516036,0.020076402,0.018700197,-0.049462426,0.0022380196,-0.009916771,-0.029898727,0.016838273,-0.037292454,0.022990718,0.003474243,-0.0244074,0.022558967,-0.009714388,0.014328722,0.00043301546,-0.02906221,0.01076678,0.018187493,-0.030195557,0.016042233,-0.01621763,0.030276509,-0.0037845636,-0.047033828,-0.014166816,-0.00075725,0.027551083,0.01180568,-0.0070969,0.014746981,0.003959962,0.009876294,0.0054002553,-0.0064695124,-0.026161386,-0.00092252955,-0.0094243055,-0.023017703,-0.02691695,-0.00426691]', NULL); - - --- --- Data for Name: api_brain_definition; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: api_keys; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: assistants; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: brain_subscription_invitations; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: brains_users; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."brains_users" ("brain_id", "rights", "default_brain", "user_id") VALUES - ('40ba47d7-51b2-4b2a-9247-89e29619efb0', 'Owner', true, '39418e3b-0258-4452-af60-7acfcc1263ff'); - - --- --- Data for Name: vectors; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: brains_vectors; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: chats; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."chats" ("chat_id", "user_id", "creation_time", "history", "chat_name") VALUES - ('ad1a4a0a-c382-420b-b7fa-7550ccfd0df0', '39418e3b-0258-4452-af60-7acfcc1263ff', '2024-08-08 22:21:58.245309', NULL, 'What is quivr'), - ('1553a034-28b9-42b4-aa53-96b32247410d', '39418e3b-0258-4452-af60-7acfcc1263ff', '2024-08-08 22:22:36.873036', NULL, 'What is quivr'), - ('82ff50e9-9088-422c-99e1-f0f3bbb9031f', '39418e3b-0258-4452-af60-7acfcc1263ff', '2024-08-08 22:23:49.364439', NULL, 'What is quivr'); - - --- --- Data for Name: chat_history; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."chat_history" ("message_id", "chat_id", "user_message", "assistant", "message_time", "brain_id", "prompt_id", "metadata", "thumbs") VALUES - ('7da3b6b2-18dd-4a6c-8306-db0a10cbba97', '1553a034-28b9-42b4-aa53-96b32247410d', 'What is quivr ? ', 'Quivr, as the name you''ve referred to me by, is a creative play on the word "quiver," which suggests a quick, agile, and responsive quality. In this context, however, it appears to be a fictional or conceptual name given to me, your assistant. As your assistant, my role is to provide information, answer questions, and assist with tasks to the best of my ability. I''m designed to be responsive and helpful, much like the implied characteristics of the name Quivr. If there''s a specific aspect or domain related to "Quivr" you''re inquiring about, feel free to specify, and I''ll do my best to provide a relevant answer.', '2024-08-08 22:22:43.510436', NULL, NULL, '{"sources": [], "citations": null, "metadata_model": {"name": "gpt-4-0125-preview", "brain_id": "73cbbb2b-ee93-ff85-9f02-a9197557ea38", "image_url": "https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png", "brain_name": "gpt-4-0125-preview", "description": "Default Description", "display_name": "f9306c02-457c-4a96-b7ba-883735d9e92f"}, "followup_questions": null}', NULL), - ('f29aa329-3b22-4d18-ab41-73f9d5449afd', '82ff50e9-9088-422c-99e1-f0f3bbb9031f', 'What is quivr ? ', 'Quivr is a helpful assistant.', '2024-08-08 22:23:51.328256', '40ba47d7-51b2-4b2a-9247-89e29619efb0', NULL, '{"sources": [], "citations": [], "metadata_model": null, "followup_questions": []}', NULL); - - --- --- Data for Name: composite_brain_connections; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: integrations; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."integrations" ("created_at", "integration_name", "integration_logo_url", "connection_settings", "id", "description", "integration_type", "max_files", "information", "tags", "allow_model_change", "integration_display_name", "onboarding_brain") VALUES - ('2024-03-06 21:21:07.251232+00', 'doc', 'https://quivr-cms.s3.eu-west-3.amazonaws.com/225911_200_46634039e4.png', NULL, 'b37a2275-61b3-460b-b4ab-94dfdf3642fb', 'Default description', 'custom', 5000, NULL, '{}', true, 'Brain', true); - - --- --- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."users" ("id", "email", "onboarded") VALUES - ('39418e3b-0258-4452-af60-7acfcc1263ff', 'admin@quivr.app', false); - - --- --- Data for Name: integrations_user; Type: TABLE DATA; Schema: public; Owner: postgres --- - --- --- Data for Name: models; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."models" ("name", "price", "max_input", "max_output", "description", "display_name", "image_url", "default", "endpoint_url", "env_variable_name") VALUES - ('gpt-4-0125-preview', 1, 4000, 4000, 'Default Description', 'GPT4', 'https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png', false, 'https://api.openai.com/v1', 'OPENAI_API_KEY'), - ('gpt-3.5-turbo-0125', 1, 10000, 1000, 'Default Description', 'GPT-3.5', 'https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png', true, 'https://api.openai.com/v1', 'OPENAI_API_KEY'); - - --- --- Data for Name: notifications; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."notifications" ("id", "datetime", "status", "archived", "description", "read", "title", "user_id", "bulk_id", "brain_id", "category") VALUES - ('a6ded397-e0b7-4881-bc1f-4899a8831cc6', '2024-08-08 22:21:07.851153+00', 'success', false, 'Your file has been properly uploaded!', false, 'Regles detiquetage.pdf', '39418e3b-0258-4452-af60-7acfcc1263ff', '6e25cdb3-d915-4adf-ae69-189f9d0b303f', '79e934ce-9c5a-447a-a4fd-4efe35de9129', 'upload'); - - --- --- Data for Name: product_to_features; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: syncs_user; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: syncs_active; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: syncs_files; Type: TABLE DATA; Schema: public; Owner: postgres --- - - - --- --- Data for Name: user_daily_usage; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."user_daily_usage" ("user_id", "email", "date", "daily_requests_count") VALUES - ('39418e3b-0258-4452-af60-7acfcc1263ff', 'admin@quivr.app', '20240808', 3); - - --- --- Data for Name: user_identity; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."user_identity" ("user_id", "openai_api_key", "company", "onboarded", "username", "company_size", "usage_purpose") VALUES - ('39418e3b-0258-4452-af60-7acfcc1263ff', NULL, 'Stan', true, 'Stan', NULL, ''); - - --- --- Data for Name: user_settings; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."user_settings" ("user_id", "max_brains", "max_brain_size", "is_premium", "api_access", "monthly_chat_credit", "last_stripe_check") VALUES - ('39418e3b-0258-4452-af60-7acfcc1263ff', 10, 50000000, false, false, 100, NULL); - - --- --- Name: refresh_tokens_id_seq; Type: SEQUENCE SET; Schema: auth; Owner: supabase_auth_admin --- - -SELECT pg_catalog.setval('"auth"."refresh_tokens_id_seq"', 13, true); - - --- --- Name: integrations_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."integrations_user_id_seq"', 6, true); - - --- --- Name: product_to_features_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."product_to_features_id_seq"', 1, false); - - --- --- Name: syncs_active_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."syncs_active_id_seq"', 1, false); - - --- --- Name: syncs_files_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."syncs_files_id_seq"', 1, false); - - --- --- Name: syncs_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."syncs_user_id_seq"', 1, false); - - --- --- PostgreSQL database dump complete --- - -RESET ALL; diff --git a/backend/worker/.gitignore b/backend/worker/.gitignore deleted file mode 100644 index ae8554dec..000000000 --- a/backend/worker/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# python generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info - -# venv -.venv diff --git a/backend/worker/Dockerfile b/backend/worker/Dockerfile deleted file mode 100644 index 9d49a7466..000000000 --- a/backend/worker/Dockerfile +++ /dev/null @@ -1,55 +0,0 @@ -FROM python:3.11.6-slim-bullseye AS BUILDER - -ARG DEV_MODE -ENV DEV_MODE=$DEV_MODE - -# Install GEOS library, Rust, and other dependencies, then clean up -RUN apt-get clean && apt-get update && apt-get install -y \ - libgeos-dev \ - libcurl4-openssl-dev \ - libssl-dev \ - binutils \ - curl \ - git \ - gcc \ - autoconf \ - automake \ - build-essential \ - libtool \ - python-dev \ - wget \ - # Additional dependencies for document handling - libmagic-dev \ - tesseract-ocr \ - poppler-utils \ - tesseract-ocr \ - libreoffice \ - libpq-dev \ - pandoc && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false - -WORKDIR /code/worker -# Add Rust binaries to the PATH -ENV PATH="/root/.cargo/bin:${PATH}" \ - POETRY_CACHE_DIR=/tmp/poetry_cache \ - PYTHONDONTWRITEBYTECODE=1 \ - POETRY_VIRTUALENVS_PATH=/code/worker/.venv-docker - -COPY . /code/ - - - -# Cache playwright dependency -RUN poetry run pip install playwright && playwright install --with-deps - -# Run install -RUN poetry install && rm -rf $POETRY_CACHE_DIR - -RUN poetry run python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" - -ENV PYTHONPATH=/code/worker diff --git a/backend/worker/Dockerfile.dev b/backend/worker/Dockerfile.dev deleted file mode 100644 index a5f9848dc..000000000 --- a/backend/worker/Dockerfile.dev +++ /dev/null @@ -1,55 +0,0 @@ -FROM python:3.11.6-slim-bullseye AS BUILDER - -ARG DEV_MODE -ENV DEV_MODE=$DEV_MODE - -RUN apt-get clean && apt-get update && apt-get install -y \ - libgeos-dev \ - libcurl4-openssl-dev \ - libssl-dev \ - binutils \ - curl \ - git \ - gcc \ - autoconf \ - automake \ - build-essential \ - libtool \ - python-dev \ - wget \ - # Additional dependencies for document handling - libmagic-dev \ - tesseract-ocr \ - poppler-utils \ - tesseract-ocr \ - libreoffice \ - libpq-dev \ - pandoc && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ - cd /usr/local/bin && \ - ln -s /opt/poetry/bin/poetry && \ - poetry config virtualenvs.create false - -WORKDIR /code/worker -# Add Rust binaries to the PATH -ENV PATH="/root/.cargo/bin:${PATH}" \ - POETRY_CACHE_DIR=/tmp/poetry_cache \ - PYTHONDONTWRITEBYTECODE=1 \ - POETRY_VIRTUALENVS_PATH=/code/worker/.venv-docker - -ENV PYTHONPATH=/code/worker - - -# WORKER -COPY worker/pyproject.toml worker/poetry.lock* /code/worker/ - -# Cache playwright dependency -RUN poetry run pip install playwright && playwright install --with-deps -# Run install -RUN poetry install --no-directory --no-root --with dev,test && \ - rm -rf $POETRY_CACHE_DIR - -RUN poetry run python -c "from unstructured.nlp.tokenize import download_nltk_packages; download_nltk_packages()" -RUN poetry run python -c "import nltk;nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng')" \ No newline at end of file diff --git a/backend/worker/README.md b/backend/worker/README.md deleted file mode 100644 index cd2b3d4e0..000000000 --- a/backend/worker/README.md +++ /dev/null @@ -1 +0,0 @@ -# quivr-worker package diff --git a/backend/worker/diff-assistant/.env.exemple b/backend/worker/diff-assistant/.env.exemple deleted file mode 100644 index 72086a545..000000000 --- a/backend/worker/diff-assistant/.env.exemple +++ /dev/null @@ -1,2 +0,0 @@ -OPENAI_API_KEY = myopenaikey -LLAMA_PARSE_API_KEY = myllamaparsekey \ No newline at end of file diff --git a/backend/worker/diff-assistant/.gitignore b/backend/worker/diff-assistant/.gitignore deleted file mode 100644 index f1a81184a..000000000 --- a/backend/worker/diff-assistant/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# python generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info - -# venv -.venv -.env -.DS_Store - -#pkl -*.pkl diff --git a/backend/worker/diff-assistant/README.md b/backend/worker/diff-assistant/README.md deleted file mode 100644 index 32d85962e..000000000 --- a/backend/worker/diff-assistant/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# diff-assistant - -Describe your project here. diff --git a/backend/worker/diff-assistant/data/cdc/Cas2-1-2_MUFFIN_MIRTYLLE.pdf b/backend/worker/diff-assistant/data/cdc/Cas2-1-2_MUFFIN_MIRTYLLE.pdf deleted file mode 100644 index 8bc6268fa..000000000 Binary files a/backend/worker/diff-assistant/data/cdc/Cas2-1-2_MUFFIN_MIRTYLLE.pdf and /dev/null differ diff --git a/backend/worker/diff-assistant/data/cdc/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.docx b/backend/worker/diff-assistant/data/cdc/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.docx deleted file mode 100644 index f24db287c..000000000 Binary files a/backend/worker/diff-assistant/data/cdc/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.docx and /dev/null differ diff --git a/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AP_Merveilleux chocolat blanc.docx b/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AP_Merveilleux chocolat blanc.docx deleted file mode 100644 index 51c44e640..000000000 Binary files a/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AP_Merveilleux chocolat blanc.docx and /dev/null differ diff --git a/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AV_Merveilleux chocolat blanc.docx b/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AV_Merveilleux chocolat blanc.docx deleted file mode 100644 index 4332d9fee..000000000 Binary files a/backend/worker/diff-assistant/data/cdc/Cas3-1-1_AV_Merveilleux chocolat blanc.docx and /dev/null differ diff --git a/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AP.pdf b/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AP.pdf deleted file mode 100644 index 5b7996051..000000000 Binary files a/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AP.pdf and /dev/null differ diff --git a/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AV.pdf b/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AV.pdf deleted file mode 100644 index 0b929186f..000000000 Binary files a/backend/worker/diff-assistant/data/etiquettes/Cas3-2-3-AV.pdf and /dev/null differ diff --git a/backend/worker/diff-assistant/data/etiquettes/etiquette_0_after.pdf b/backend/worker/diff-assistant/data/etiquettes/etiquette_0_after.pdf deleted file mode 100644 index b281a3182..000000000 Binary files a/backend/worker/diff-assistant/data/etiquettes/etiquette_0_after.pdf and /dev/null differ diff --git a/backend/worker/diff-assistant/data/etiquettes/etiquette_0_before.pdf b/backend/worker/diff-assistant/data/etiquettes/etiquette_0_before.pdf deleted file mode 100644 index 464cb7cdb..000000000 Binary files a/backend/worker/diff-assistant/data/etiquettes/etiquette_0_before.pdf and /dev/null differ diff --git a/backend/worker/diff-assistant/data/fiche_dev_produit/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.xlsx b/backend/worker/diff-assistant/data/fiche_dev_produit/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.xlsx deleted file mode 100644 index a7532e845..000000000 Binary files a/backend/worker/diff-assistant/data/fiche_dev_produit/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.xlsx and /dev/null differ diff --git a/backend/worker/diff-assistant/notebooks/use_case_3/test_etiquette.ipynb b/backend/worker/diff-assistant/notebooks/use_case_3/test_etiquette.ipynb deleted file mode 100644 index 623448895..000000000 --- a/backend/worker/diff-assistant/notebooks/use_case_3/test_etiquette.ipynb +++ /dev/null @@ -1,958 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "from diff_algorithm import DiffAlgorithm\n", - "from parser import DeadlyParser\n", - "\n", - "file_path_after = \"/Users/chloed./Documents/quivr/diff-assistant/src/cdp3/test_docs/etiquette_0_before.pdf\"\n", - "file_path_before = \"/Users/chloed./Documents/quivr/diff-assistant/src/cdp3/test_docs/etiquette_0_after.pdf\"\n", - "complex_file = \"/Users/chloed./Documents/quivr/diff-assistant/src/cdp3/test_docs/Cas3-2-3.pdf\"" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "parser = DeadlyParser()\n", - "parsed_before = parser.parse(file_path_before)\n", - "parsed_after = parser.parse(file_path_after)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "text_before = parsed_before.render()\n", - "text_after = parsed_after.render()" - ] - }, - { - "cell_type": "code", - "execution_count": 61, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CUDA device False\n", - "\n", - "0: 1024x800 2 Pictures, 2 Section-headers, 18 Texts, 1091.6ms\n", - "Speed: 24.9ms preprocess, 1091.6ms inference, 84.6ms postprocess per image at shape (1, 3, 1024, 800)\n" - ] - } - ], - "source": [ - "from PIL import Image\n", - "import pypdfium2 as pdfium\n", - "import torchvision.transforms as transforms\n", - "\n", - "import torch\n", - "from ultralytics import YOLOv10\n", - "\n", - "print(\"CUDA device\", torch.cuda.is_available())\n", - "\n", - "device = torch.device(\"mps\") # Default CUDA device\n", - "\n", - "model = YOLOv10(\"./yolov10x_best.pt\").to(device)\n", - "\n", - "pdf = pdfium.PdfDocument(file_path_after)\n", - "page = pdf[0] # load a page\n", - "\n", - "bitmap = page.render(scale=500 / 72)\n", - "\n", - "pil_image = bitmap.to_pil()\n", - "\n", - "# Create a transform to convert PIL image to tensor\n", - "to_tensor = transforms.ToTensor()\n", - "\n", - "# Convert PIL image to tensor (this also normalizes values to [0, 1])\n", - "tensor_image = to_tensor(pil_image)\n", - "\n", - "# Add batch dimension\n", - "tensor_image = tensor_image.unsqueeze(0).to(device)\n", - "\n", - "# Assuming your model is already on the CUDA device\n", - "model = model.to(device)\n", - "\n", - "# Perform inference\n", - "with torch.no_grad():\n", - " results = model.predict(source=pil_image, imgsz=1024, conf=0.35, batch=1)\n", - "\n", - "\n", - "annotated_image = results[0].plot()[:, :, ::-1]\n", - "\n", - "im = Image.fromarray(annotated_image)\n", - "\n", - "im.show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "tensor([0.8352, 0.8235, 0.8203, 0.8113, 0.7984, 0.7860, 0.6394, 0.5778, 0.5666, 0.5546, 0.5365, 0.5300, 0.4666, 0.4322, 0.4222, 0.3932, 0.3926, 0.3901], device='mps:0')\n", - "tensor([6., 9., 7., 9., 6., 9., 6., 9., 9., 9., 9., 9., 9., 9., 9., 9., 9., 9.], device='mps:0')\n" - ] - } - ], - "source": [ - "print(results[0].boxes.conf)\n", - "print(results[0].boxes.cls)\n", - "results[0].boxes.xyxyn" - ] - }, - { - "cell_type": "code", - "execution_count": 157, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.messages import HumanMessage, SystemMessage\n", - "from io import BytesIO\n", - "import base64\n", - "def check_transcription(file_path, text):\n", - " pdf = pdfium.PdfDocument(file_path)\n", - " page = pdf[0] # load a page\n", - " \n", - " bitmap = page.render(scale=500 / 72)\n", - " \n", - " pil_image_before = bitmap.to_pil()\n", - " \n", - " buffered = BytesIO()\n", - " pil_image_before.save(buffered, format=\"PNG\")\n", - " img_str = base64.b64encode(buffered.getvalue()).decode()\n", - " \n", - " chat = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", - " result = chat.invoke(\n", - " [\n", - " HumanMessage(\n", - " content=[\n", - " {\"type\": \"text\", \"text\": f\"Can you correct this entire text retranscription, respond only with the corrected transcription: {text}\"},\n", - " {\n", - " \"type\": \"image_url\",\n", - " \"image_url\": {\n", - " \"url\": f\"data:image/jpeg;base64,{img_str}\",\n", - " \"detail\": \"auto\",\n", - " },\n", - " },\n", - " ]\n", - " )\n", - " ]\n", - " )\n", - " return result" - ] - }, - { - "cell_type": "code", - "execution_count": 158, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "result_before = check_transcription(file_path_before, text_before)\n", - "result_after = check_transcription(file_path_after, text_after)" - ] - }, - { - "cell_type": "code", - "execution_count": 168, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Coup de pâtes\n", - "TRADITION & INNOVATION\n", - "\n", - "50 CREPES FINES SUCREES AU RHUM NEGRITA® (PLIEES EN QUATRE) D270 55g\n", - "50 Thin crêpes sweetened with rum Negrita® (folded in four) D270 55g\n", - "\n", - "25514\n", - "Rhum NEGRITA\n", - "50 Crêpes fines sucrées au rhum cuites, surgelées -\n", - "50 Crêpes sweetened with rum, baked, frozen\n", - "\n", - "Ingrédients : LAIT entier, farine de BLE, sucre de canne 16.4%, Å’UFS entiers*, beurre concentré (LAIT), eau, rhum Negrita (colorant: E150a) 3.6%, sel, poudres à lever: E500-E331-amidon de BLE.\n", - "* Å’ufs issus de poules élevées au sol\n", - "\n", - "Ingredients : Whole MILK, WHEAT flour, cane sugar 16.4%, whole EGGS*, concentrated butter (MILK), water, Negrita rum (colouring: E150a) 3.6%, salt, raising agents: E500-E331-WHEAT starch.\n", - "* Barn eggs\n", - "\n", - "Conseil d'utilisation : Décongeler le produit 1 heure entre 0° et 4°C. Après décongélation et maintien à 4°C, le produit se conserve au maximum pendant 24 heures. Suggestion: possibilité de décongeler les crêpes 30 secondes au four à micro-ondes.\n", - "How to prepare the product: Defrost the product 1 hour at 0°C - +4°C. After thawing, preserve the product at +4°C for 24 hours maximum. Suggestion: Defrost the crêpe 30 sec in the microwave.\n", - "\n", - "Informations nutritionnelles pour 100g / Average nutritional values for 100g:\n", - "Valeur énergétique/Energy: 1495 kJ / 356 kcal\n", - "Matières grasses totales/Fat (g): 11.4\n", - "- dont Acides Gras Saturés/of which saturated fatty acids (g): 5.9\n", - "Glucides/Carbohydrates (g): 49.5\n", - "- dont sucres/of which sugar (g): 25.2\n", - "Protéines/Proteins (g): 8.0\n", - "Sel/Salt (g): 0.45\n", - "\n", - "A conserver à -18°C : Ne jamais recongeler un produit décongelé\n", - "Store at -18°C: Don't refreeze, once defrosted\n", - "\n", - "Coup de pâtes\n", - "50 CREPES FINES SUCREES AU RHUM NEGRITA® (PLIEES EN QUATRE) D270 55g\n", - "50 Crêpes fines sucrées au rhum cuites, surgelées -\n", - "50 Crêpes sweetened with rum, baked, frozen\n", - "\n", - "N° DE LOT / BATCH : 116241 13:17\n", - "A consommer de préférence avant le / Best before : 25/10/2025\n", - "\n", - "25514\n", - "FAB : A04A\n", - "\n", - "(01)03604380255141(15)251025(10)116241(91)0316175\n", - "EAN No: 03604380255141\n", - "\n", - "Poids net / Net weight : 2750 g\n", - "\n", - "C.I: 7142 COUP DE PATES S.A.S. ZAC DU BEL AIR - 14-16 AVENUE JOSEPH PAXTON - FERRIERES EN BRIE - 77164 MARNE LA VALLEE CEDEX 3 - FRANCE\n" - ] - } - ], - "source": [ - "print(result_after.content)" - ] - }, - { - "cell_type": "code", - "execution_count": 171, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(0, '50 CREPES FINES SUCREES AU\\nCoupdegal'),\n", - " (-1, 'g'),\n", - " (1, 'o'),\n", - " (0, '\\nRHUM NEGRITAO (PLIEES EN QUATRE)\\nTRA'),\n", - " (-1, 'C'),\n", - " (1, 'D'),\n", - " (0, 'ITION '),\n", - " (-1, '&'),\n", - " (1, 'a'),\n", - " (0, ' INNO'),\n", - " (-1, 'V'),\n", - " (1, 'Y'),\n", - " (0, 'AT'),\n", - " (-1, 'IG'),\n", - " (1, ':O'),\n", - " (0, 'N\\nD270 55g\\n50 Thin cr'),\n", - " (-1, 'ê'),\n", - " (1, 'è'),\n", - " (0, 'pes sweetened with rum Negrita'),\n", - " (-1, 'g'),\n", - " (1, 'e'),\n", - " (0, '\\n(folded in four) D270 55g\\n25514 R'),\n", - " (-1, 'k'),\n", - " (1, 'h'),\n", - " (0, 'um'),\n", - " (-1, 'y'),\n", - " (0, '\\n'),\n", - " (1, 'NEGRITA '),\n", - " (0, '50 Crêpes fines sucrées au rhum cuites, surgelées -\\n'),\n", - " (-1, 'NEGRITA\\n'),\n", - " (0, '50 Cr'),\n", - " (-1, 'è'),\n", - " (1, 'ê'),\n", - " (0,\n", - " 'pes sweetened with rum, baked, frozen\\nIngrédients : LAIT entier, farine de BLE, sucre de canne 16.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0,\n", - " '%, CEUFS entiers*,\\nbeurre concentré (LAIT), eau, rhum Negrita (colorant: E150a) 3.'),\n", - " (-1, '7'),\n", - " (1, '6'),\n", - " (0,\n", - " '%, sel, poudres à\\nlever: E500-E331-amidon de BLE.\\n* CEufs issus de poules élevées au sol\\nIngredients : Whole MILK, WHEAT flour, cane sugar 16.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0, '%, whole EGGS*, con'),\n", - " (-1, 'c'),\n", - " (1, 'ç'),\n", - " (0, 'entrated\\nbutter (MILK), water, Negrita rum (colouring: E150a) 3.'),\n", - " (-1, '7'),\n", - " (1, '6'),\n", - " (0,\n", - " \"%, salt, raising agents:\\nE500-E331-WHEAT starch.\\n* Barn eggs\\nConseil d'utilisation : Décongeler le produit 1 heure entre 0° et 4°C. Après décongélation et\\nmaintien à 4°C, le produit se conserve au maximum pendant 24 heures\"),\n", - " (1, '.'),\n", - " (0, '\\nSuggestion: possibilité de décongeler les cr'),\n", - " (-1, 'è'),\n", - " (1, 'é'),\n", - " (0, 'pes 30 secondes au four à micr'),\n", - " (-1, 'o'),\n", - " (1, 'c'),\n", - " (0, '-ondes.\\n'),\n", - " (-1, 'BPA le 24.09.2020 '),\n", - " (0, \"How to prepare the products: Defrost the product 1 hour at 0'C-+4\"),\n", - " (1, '°'),\n", - " (0, 'C. After thawing,\\npreserve the product at +4'),\n", - " (-1, '°'),\n", - " (1, '*'),\n", - " (0,\n", - " 'C for 24 hours maximum. Suggestion: Defrost the crèpe 30 sec\\nin the microwave.\\nInformations nutritionnelles pour 1 Average nutritional values for 100g:\\nValeur '),\n", - " (-1, 'e'),\n", - " (1, 'é'),\n", - " (0, 'nerg'),\n", - " (-1, 'e'),\n", - " (1, 'é'),\n", - " (0, 'tique/Energy: 149'),\n", - " (-1, '7'),\n", - " (1, '5'),\n", - " (0, ' kJ / 356 kcal\\nMatières grasses totales/Fat (g): 11.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0, '\\n- dont Acides Gras Saturés/of which saturated fatty acids (g): '),\n", - " (-1, '6'),\n", - " (1, '5'),\n", - " (0, '.'),\n", - " (-1, '1'),\n", - " (1, '9'),\n", - " (0, '\\nGiu'),\n", - " (-1, 'c'),\n", - " (1, 'ri'),\n", - " (0, 'des/Car'),\n", - " (-1, 'p'),\n", - " (1, 'b'),\n", - " (0, 'o'),\n", - " (-1, 'n'),\n", - " (1, 'h'),\n", - " (0, 'y'),\n", - " (-1, 'ct'),\n", - " (1, 'di'),\n", - " (0, 'ates (g): 4'),\n", - " (-1, '8'),\n", - " (1, '9'),\n", - " (0, '.'),\n", - " (-1, '9'),\n", - " (1, '5'),\n", - " (0, '\\n'),\n", - " (-1, '- '),\n", - " (0, 'dont sucres/of which sugar (g): 2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '.'),\n", - " (-1, '1'),\n", - " (1, '2'),\n", - " (0, '\\nProtéines'),\n", - " (-1, '/'),\n", - " (0, 'Proteins (g): 8.0\\nSel/Salt (g): 0.4'),\n", - " (-1, '8'),\n", - " (1, '5'),\n", - " (0,\n", - " \"\\nA conserver à -18°C : Ne jamais recongeler un produit décongelé\\nStore at -18°C: Don't refreeze, once defrosted\\n\"),\n", - " (-1, 'Fabriqué en France - Made in France\\n'),\n", - " (0, 'Cou'),\n", - " (-1, 'y'),\n", - " (0, 'pde'),\n", - " (-1, 'g'),\n", - " (1, ' '),\n", - " (0, 'al'),\n", - " (-1, 'g'),\n", - " (0, '\\n50 CREPES FINES SUCREES AU RHUM\\nNEGRITA'),\n", - " (-1, 'O'),\n", - " (1, 'B'),\n", - " (0, ' (PLIEES EN QUATRE) D270\\nT'),\n", - " (-1, 'R'),\n", - " (1, 'W'),\n", - " (0, 'ADITION & INNOVAT'),\n", - " (-1, ':'),\n", - " (1, 'I'),\n", - " (0, 'ON\\n55g\\n50 Cr'),\n", - " (-1, 'ê'),\n", - " (1, 'è'),\n", - " (0,\n", - " 'pes fines sucrées au rhum cuites, surgelées -\\nNo DE LOTI\\n50 Crèpes sweetened with rum, baked, frozen\\nBATCH : '),\n", - " (-1, '084'),\n", - " (1, '116'),\n", - " (0, '2'),\n", - " (-1, '0'),\n", - " (1, '4'),\n", - " (0, '1 1'),\n", - " (-1, '5'),\n", - " (1, '3'),\n", - " (0, ':'),\n", - " (-1, '4'),\n", - " (1, '1'),\n", - " (0, '7\\nA consommer de pr'),\n", - " (-1, 'è'),\n", - " (1, 'é'),\n", - " (0, 'f'),\n", - " (-1, 'è'),\n", - " (1, 'é'),\n", - " (0, 'rence avant'),\n", - " (-1, ' '),\n", - " (0, 'le '),\n", - " (-1, 'I'),\n", - " (1, '/'),\n", - " (0, '\\n25514\\nBest before : 2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '/'),\n", - " (1, '1'),\n", - " (0, '0'),\n", - " (-1, '9'),\n", - " (0, '/202'),\n", - " (-1, '1'),\n", - " (1, '5'),\n", - " (0, '\\n'),\n", - " (1, 'FAB :\\nA'),\n", - " (0, '0'),\n", - " (1, '4A\\n'),\n", - " (0, '0'),\n", - " (-1, '9'),\n", - " (1, '1.'),\n", - " (0, '0'),\n", - " (1, '9'),\n", - " (0, '80'),\n", - " (-1, '43'),\n", - " (1, '.9'),\n", - " (0, '80'),\n", - " (1, '2'),\n", - " (0, '55141052'),\n", - " (1, '5'),\n", - " (0, '10'),\n", - " (-1, '9'),\n", - " (0, '2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '10'),\n", - " (-1, '08'),\n", - " (1, '1162'),\n", - " (0, '4'),\n", - " (-1, '20'),\n", - " (0, '1 (91)0316'),\n", - " (-1, '4'),\n", - " (1, '1'),\n", - " (0, '7'),\n", - " (-1, '6'),\n", - " (1, '5'),\n", - " (0, '\\nEAN No: 03604380255141'),\n", - " (-1, ' FAB : 00001 '),\n", - " (1, '\\n'),\n", - " (0, 'Poids net'),\n", - " (-1, '\\n:\\n'),\n", - " (1, '! '),\n", - " (0, '2750\\nNet weight'),\n", - " (1, ': :'),\n", - " (0, '\\ng\\n'),\n", - " (-1, '\\n'),\n", - " (1, 'Ci: 7142 '),\n", - " (0, 'COUP DE'),\n", - " (1, 'F'),\n", - " (0, ' PATES'),\n", - " (-1, 'E'),\n", - " (1, 'O'),\n", - " (0, ' S'),\n", - " (-1, '.'),\n", - " (0, 'A.S'),\n", - " (-1, '-;'),\n", - " (0, ' ZA'),\n", - " (-1, 'C'),\n", - " (1, 'Ç'),\n", - " (0, ' DU BEL AIR'),\n", - " (-1, '-'),\n", - " (0, ' 14-16 AVENUE'),\n", - " (1, '.'),\n", - " (0, ' '),\n", - " (-1, 'J'),\n", - " (1, 'V'),\n", - " (0, 'OSEPH'),\n", - " (-1, ' '),\n", - " (0, 'PAXTON-\\nFERRIERES EN'),\n", - " (-1, 'I'),\n", - " (0, ' BRIE 77'),\n", - " (-1, '6'),\n", - " (1, '8'),\n", - " (0, '14 MARNE LA VALLEE CEDEX 3'),\n", - " (1, '- FRANÇE')]" - ] - }, - "execution_count": 171, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dmp= DiffAlgorithm()\n", - "diff_main = dmp.diff_main(result_before.content, result_after.content)\n", - "#diff_main = dmp.diff_main(text_before, text_after)\n", - "#result = dmp.to_pretty_json(diff_main, parsed_before)\n", - "diff_main" - ] - }, - { - "cell_type": "code", - "execution_count": 172, - "metadata": {}, - "outputs": [], - "source": [ - "#split differences and send to llm \n", - "cleaned_diff = []\n", - "for cat, content in diff_main:\n", - " if content.strip() and content != \"\\n\":\n", - " cleaned_diff.append((cat, content))" - ] - }, - { - "cell_type": "code", - "execution_count": 173, - "metadata": {}, - "outputs": [], - "source": [ - "def format_difference(main_diff):\n", - " text_modified = \"\"\n", - " sub_stack = 0\n", - " for op, data in main_diff:\n", - " if op == 0: \n", - " text_modified += data if sub_stack == 0 else f\"_]] {data}\"\n", - " elif op == -1: \n", - " if sub_stack == 0:\n", - " text_modified += f\"[[{data}->\"\n", - " sub_stack += 1\n", - " else:\n", - " text_modified += f\"{data}->\"\n", - " elif op == 1: \n", - " if sub_stack > 0:\n", - " text_modified += f\"{data}]]\"\n", - " sub_stack -= 1\n", - " else:\n", - " text_modified += f\"[[ _ ->{data}]]\"\n", - " return text_modified" - ] - }, - { - "cell_type": "code", - "execution_count": 174, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\"50 CREPES FINES SUCREES AU\\nCoupdegal[[g->o]]\\nRHUM NEGRITAO (PLIEES EN QUATRE)\\nTRA[[C->D]]ITION [[&->a]] INNO[[V->Y]]AT[[IG->:O]]N\\nD270 55g\\n50 Thin cr[[ê->è]]pes sweetened with rum Negrita[[g->e]]\\n(folded in four) D270 55g\\n25514 R[[k->h]]um[[y->NEGRITA ]]50 Crêpes fines sucrées au rhum cuites, surgelées -\\n[[NEGRITA\\n->_]] 50 Crè->ê]]pes sweetened with rum, baked, frozen\\nIngrédients : LAIT entier, farine de BLE, sucre de canne 16.[[6->4]]%, CEUFS entiers*,\\nbeurre concentré (LAIT), eau, rhum Negrita (colorant: E150a) 3.[[7->6]]%, sel, poudres à\\nlever: E500-E331-amidon de BLE.\\n* CEufs issus de poules élevées au sol\\nIngredients : Whole MILK, WHEAT flour, cane sugar 16.[[6->4]]%, whole EGGS*, con[[c->ç]]entrated\\nbutter (MILK), water, Negrita rum (colouring: E150a) 3.[[7->6]]%, salt, raising agents:\\nE500-E331-WHEAT starch.\\n* Barn eggs\\nConseil d'utilisation : Décongeler le produit 1 heure entre 0° et 4°C. Après décongélation et\\nmaintien à 4°C, le produit se conserve au maximum pendant 24 heures[[ _ ->.]]\\nSuggestion: possibilité de décongeler les cr[[è->é]]pes 30 secondes au four à micr[[o->c]]-ondes.\\n[[BPA le 24.09.2020 ->_]] How to prepare the products: Defrost the product 1 hour at 0'C-+4°]]C. After thawing,\\npreserve the product at +4[[°->*]]C for 24 hours maximum. Suggestion: Defrost the crèpe 30 sec\\nin the microwave.\\nInformations nutritionnelles pour 1 Average nutritional values for 100g:\\nValeur [[e->é]]nerg[[e->é]]tique/Energy: 149[[7->5]] kJ / 356 kcal\\nMatières grasses totales/Fat (g): 11.[[6->4]]\\n- dont Acides Gras Saturés/of which saturated fatty acids (g): [[6->5]].[[1->9]]\\nGiu[[c->ri]]des/Car[[p->b]]o[[n->h]]y[[ct->di]]ates (g): 4[[8->9]].[[9->5]][[- ->_]] dont sucres/of which sugar (g): 24->5]].[[1->2]]\\nProtéines[[/->_]] Proteins (g): 8.0\\nSel/Salt (g): 0.48->5]]\\nA conserver à -18°C : Ne jamais recongeler un produit décongelé\\nStore at -18°C: Don't refreeze, once defrosted\\n[[Fabriqué en France - Made in France\\n->_]] Couy->_]] pdeg->_]] alg->_]] \\n50 CREPES FINES SUCREES AU RHUM\\nNEGRITAO->B]] (PLIEES EN QUATRE) D270\\nT[[R->W]]ADITION & INNOVAT[[:->I]]ON\\n55g\\n50 Cr[[ê->è]]pes fines sucrées au rhum cuites, surgelées -\\nNo DE LOTI\\n50 Crèpes sweetened with rum, baked, frozen\\nBATCH : [[084->116]]2[[0->4]]1 1[[5->3]]:[[4->1]]7\\nA consommer de pr[[è->é]]f[[è->é]]rence avantle [[I->/]]\\n25514\\nBest before : 2[[4->5]]/[[ _ ->1]]0[[9->_]] /2021->5]][[ _ ->FAB :\\nA]]0[[ _ ->4A\\n]]0[[9->1.]]0[[ _ ->9]]80[[43->.9]]80[[ _ ->2]]55141052[[ _ ->5]]10[[9->_]] 24->5]]10[[08->1162]]4[[20->_]] 1 (91)03164->1]]7[[6->5]]\\nEAN No: 03604380255141[[ FAB : 00001 ->_]] Poids net\\n:\\n->! ]]2750\\nNet weight[[ _ ->: :]]\\ng\\n[[ _ ->Ci: 7142 ]]COUP DE[[ _ ->F]] PATES[[E->O]] S[[.->_]] A.S-;->_]] ZAC->Ç]] DU BEL AIR[[-->_]] 14-16 AVENUE.]][[J->V]]OSEPHPAXTON-\\nFERRIERES EN[[I->_]] BRIE 776->8]]14 MARNE LA VALLEE CEDEX 3[[ _ ->- FRANÇE]]\"" - ] - }, - "execution_count": 174, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "format_difference(cleaned_diff)" - ] - }, - { - "cell_type": "code", - "execution_count": 175, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "import os\n", - "\n", - "llm = ChatOpenAI(\n", - " model=\"gpt-4o\",\n", - " temperature=0,\n", - " max_tokens=None,\n", - " timeout=None,\n", - " max_retries=2,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 176, - "metadata": {}, - "outputs": [], - "source": [ - "section_diffs = [cleaned_diff]" - ] - }, - { - "cell_type": "code", - "execution_count": 177, - "metadata": {}, - "outputs": [], - "source": [ - "report = []\n", - "#modified_section_names = []\n", - "for section in section_diffs:\n", - " if len(section) == 1 and section[0][0] == 0:\n", - " print(\"No differences found in this section.\")\n", - " continue\n", - " else:\n", - " text_modified = format_difference(section)\n", - " #modified_section_names.append(section[0][1].split(\"\\n\")[1].split(\"#\")[-1].strip())\n", - " messages = [\n", - " (\n", - " \"human\",\n", - " f\"\"\"You are tasked with analyzing and reporting differences in text for a Quality engineer. The input text contains differences marked with special tokens. Your job is to parse these differences and create a clear, concise report.\n", - "\n", - " Here is the text containing the differences:\n", - "\n", - " \n", - " {text_modified}\n", - " \n", - "\n", - " RULE #1 : If there are no [[->]] tokens, it indicates no changes to report, inventing changes means death.\n", - " The differences are marked using the following format:\n", - " - [[before->after]] indicates a change from \"before\" to \"after\"\n", - " - If there is no \"before\" text, it indicates an addition\n", - " - If there is no \"after\" text, it indicates a deletion\n", - " - If there is no [[ ]] token, it indicates no changes to report\n", - " - Make sense of the difference and do not keep the '[' in the report.\n", - " - \"_\" alone means empty.\n", - "\n", - " Follow these steps to create your report:\n", - "\n", - " 1. Carefully read through the entire text.\n", - " 2. Identify each instance of [[ ]] tokens.\n", - " 3. For each instance, determine the modification that was made.\n", - " Present your report in the following format:\n", - " \n", - " In the section ..., the modification found are :\n", - " * the **black** cat was changed to : the **red** cat\n", - " * ...\n", - " \n", - " Note that there might be no modifications in some sections. In that case, simply state that no differences were found.\n", - "\n", - "\n", - " Remember, your goal is to create a clear and concise report that allows the Quality engineer to quickly verify the differences. Focus on accuracy and readability in your output, give every indication possible to make it easier to find the modification.\n", - " The report should be written in a professional and formal tone and in French.\"\"\",\n", - " ),\n", - " ]\n", - " response = llm.invoke(messages)\n", - " report.append(response.content)\n", - "\n", - "#print(\"The modified Sections are : \", modified_section_names)" - ] - }, - { - "cell_type": "code", - "execution_count": 178, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Dans la section \"50 CREPES FINES SUCREES AU\", les modifications trouvées sont :\n", - "* Coupdegal**g** a été changé en : Coupdegal**o**\n", - "\n", - "Dans la section \"RHUM NEGRITAO (PLIEES EN QUATRE)\", les modifications trouvées sont :\n", - "* TRA**C**ITION a été changé en : TRA**D**ITION\n", - "* TRA**&** INNO**V**ATION a été changé en : TRA**a** INNO**Y**ATION\n", - "* INNO**V**ATION a été changé en : INNO**Y**ATION\n", - "* INNO**IG**N a été changé en : INNO**:O**N\n", - "\n", - "Dans la section \"50 Thin crêpes sweetened with rum Negrita\", les modifications trouvées sont :\n", - "* cr**ê**pes a été changé en : cr**è**pes\n", - "* Negrita**g** a été changé en : Negrita**e**\n", - "\n", - "Dans la section \"25514 Rhum NEGRITA 50 Crêpes fines sucrées au rhum cuites, surgelées\", les modifications trouvées sont :\n", - "* R**k**um a été changé en : R**h**um\n", - "* Rhum**y** a été changé en : Rhum**NEGRITA**\n", - "* NEGRITA a été changé en : (supprimé)\n", - "* Crè**ê**pes a été changé en : Crè**e**pes\n", - "\n", - "Dans la section \"Ingrédients\", les modifications trouvées sont :\n", - "* sucre de canne 16.**6**% a été changé en : sucre de canne 16.**4**%\n", - "* rhum Negrita (colorant: E150a) 3.**7**% a été changé en : rhum Negrita (colorant: E150a) 3.**6**%\n", - "\n", - "Dans la section \"Ingredients\", les modifications trouvées sont :\n", - "* cane sugar 16.**6**% a été changé en : cane sugar 16.**4**%\n", - "* con**c**entrated butter a été changé en : con**ç**entrated butter\n", - "* Negrita rum (colouring: E150a) 3.**7**% a été changé en : Negrita rum (colouring: E150a) 3.**6**%\n", - "\n", - "Dans la section \"Conseil d'utilisation\", les modifications trouvées sont :\n", - "* 24 heures** _ ** a été changé en : 24 heures**.**\n", - "* cr**è**pes a été changé en : cr**é**pes\n", - "* micr**o**-ondes a été changé en : micr**c**-ondes\n", - "* BPA le 24.09.2020 a été changé en : (supprimé)\n", - "\n", - "Dans la section \"How to prepare the products\", les modifications trouvées sont :\n", - "* 0'C-+4**°**C a été changé en : 0'C-+4**C**\n", - "* +4**°**C a été changé en : +4**C**\n", - "\n", - "Dans la section \"Valeur énergétique/Energy\", les modifications trouvées sont :\n", - "* Valeur **e**nerg**e**tique a été changé en : Valeur **é**nerg**é**tique\n", - "* 149**7** kJ a été changé en : 149**5** kJ\n", - "\n", - "Dans la section \"Matières grasses totales/Fat (g)\", les modifications trouvées sont :\n", - "* 11.**6** a été changé en : 11.**4**\n", - "\n", - "Dans la section \"Acides Gras Saturés/of which saturated fatty acids (g)\", les modifications trouvées sont :\n", - "* **6**.1 a été changé en : **5**.9\n", - "\n", - "Dans la section \"Glucides/Carbohydrates (g)\", les modifications trouvées sont :\n", - "* Giu**c**des a été changé en : Giu**ri**des\n", - "* Car**p**o**n**y**ct**ates a été changé en : Car**b**o**h**y**di**ates\n", - "* 4**8**.9 a été changé en : 4**9**.5\n", - "* 24**-**1 a été changé en : 24**.2**\n", - "\n", - "Dans la section \"Protéines/Proteins (g)\", les modifications trouvées sont :\n", - "* Protéines**/** a été changé en : Protéines\n", - "\n", - "Dans la section \"Sel/Salt (g)\", les modifications trouvées sont :\n", - "* 0.48**->5** a été changé en : 0.48**5**\n", - "\n", - "Dans la section \"A conserver à -18°C\", les modifications trouvées sont :\n", - "* Fabriqué en France - Made in France a été changé en : (supprimé)\n", - "\n", - "Dans la section \"50 CREPES FINES SUCREES AU RHUM\", les modifications trouvées sont :\n", - "* NEGRITAO**->B** a été changé en : NEGRITAO**B**\n", - "* T**R**ADITION a été changé en : T**W**ADITION\n", - "* INNOVAT**:**ON a été changé en : INNOVAT**I**ON\n", - "\n", - "Dans la section \"50 Crêpes fines sucrées au rhum cuites, surgelées\", les modifications trouvées sont :\n", - "* cr**ê**pes a été changé en : cr**è**pes\n", - "\n", - "Dans la section \"BATCH\", les modifications trouvées sont :\n", - "* 084**2**0 a été changé en : 116**4**1\n", - "* 1**5**:4**7** a été changé en : 1**3**:1**7**\n", - "\n", - "Dans la section \"A consommer de préférence avant le\", les modifications trouvées sont :\n", - "* 2**4**/10/2021 a été changé en : 2**5**/10/2021\n", - "* FAB : A0 a été changé en : FAB : 4A\n", - "* 09.0 a été changé en : 1.0\n", - "* 98043 a été changé en : 980\n", - "* 255141052 a été changé en : 2551410525\n", - "* 109 a été changé en : 109\n", - "* 24.10.08 a été changé en : 24.10.1162\n", - "* 20 a été changé en : 20\n", - "* 1 (91)03164 a été changé en : 1 (91)03164-17\n", - "\n", - "Dans la section \"EAN No\", les modifications trouvées sont :\n", - "* EAN No: 03604380255141 FAB : 00001 a été changé en : EAN No: 03604380255141\n", - "\n", - "Dans la section \"Poids net\", les modifications trouvées sont :\n", - "* Poids net : 2750 a été changé en : Poids net : 2750\n", - "\n", - "Dans la section \"Net weight\", les modifications trouvées sont :\n", - "* Net weight : : a été changé en : Net weight : :\n", - "\n", - "Dans la section \"COUP DE PATES\", les modifications trouvées sont :\n", - "* COUP DE PATES a été changé en : COUP DE F PATES\n", - "* S.A.S a été changé en : S.A.S\n", - "* ZAC DU BEL AIR a été changé en : ZAC DU BEL AIR\n", - "* 14-16 AVENUE a été changé en : 14-16 AVENUE\n", - "* JOSEPH PAXTON a été changé en : JOSEPH PAXTON\n", - "* FERRIERES EN BRIE 77614 MARNE LA VALLEE CEDEX 3 a été changé en : FERRIERES EN BRIE 77614 MARNE LA VALLEE CEDEX 3\n", - "\n" - ] - } - ], - "source": [ - "print(report[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 166, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, 'Coup de pâtes\\nTRADITION & INNOVATION\\n\\n'),\n", - " (0,\n", - " '50 CREPES FINES SUCREES AU RHUM NEGRITA® (PLIEES EN QUATRE) D270 55g\\n50 Thin crêpes sweetened with rum Negrita® (folded in four) D270 55g\\n'),\n", - " (0, '25514'),\n", - " (0, 'Rhum NEGRITA\\n50 Crêpes fines sucrées au rhum cuites, surgelées -'),\n", - " (0, '50 Crêpes sweetened with rum, baked, frozen'),\n", - " (0, '\\nIngrédients : LAIT entier, farine de BLE, sucre de canne 16.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0,\n", - " '%, Å’UFS entiers*, beurre concentré (LAIT), eau, rhum Negrita (colorant: E150a) 3.'),\n", - " (-1, '7'),\n", - " (1, '6'),\n", - " (0,\n", - " '%, sel, poudres à lever: E500-E331-amidon de BLE.\\n* Å’ufs issus de poules élevées au sol\\n'),\n", - " (0, 'Ingredients : Whole MILK, WHEAT flour, cane sugar 16.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0,\n", - " '%, whole EGGS*, concentrated butter (MILK), water, Negrita rum (colouring: E150a) 3.'),\n", - " (-1, '7'),\n", - " (1, '6'),\n", - " (0, '%, salt, raising agents: E500-E331-WHEAT starch.\\n* Barn eggs\\n'),\n", - " (0,\n", - " \"Conseil d'utilisation : Décongeler le produit 1 heure entre 0° et 4°C. Après décongélation et maintien à 4°C, le produit se conserve au maximum pendant 24 heures\"),\n", - " (1, '. '),\n", - " (0,\n", - " 'Suggestion: possibilité de décongeler les crêpes 30 secondes au four à micro-ondes.\\n'),\n", - " (-1, 'BPA le 24.09.2020 '),\n", - " (0, 'How to prepare the product'),\n", - " (-1, 's'),\n", - " (0, ': Defrost the product 1 hour at 0°C'),\n", - " (0, '-'),\n", - " (1, ' +'),\n", - " (0, '4°C. After thawing, preserve the product at '),\n", - " (1, '+'),\n", - " (0,\n", - " '4°C for 24 hours maximum. Suggestion: Defrost the crêpe 30 sec in the microwave.\\n'),\n", - " (0,\n", - " 'Informations nutritionnelles pour 100g / Average nutritional values for 100g:\\nValeur énergétique/Energy: 149'),\n", - " (-1, '7'),\n", - " (1, '5'),\n", - " (0, ' kJ / 356 kcal\\nMatières grasses totales/Fat (g): 11.'),\n", - " (-1, '6'),\n", - " (1, '4'),\n", - " (0, '\\n- dont Acides Gras Saturés/of which saturated fatty acids (g): '),\n", - " (-1, '6'),\n", - " (1, '5'),\n", - " (0, '.'),\n", - " (-1, '1'),\n", - " (1, '9'),\n", - " (0, '\\nGlucides/Carbohydrates (g): 4'),\n", - " (-1, '8'),\n", - " (1, '9'),\n", - " (0, '.'),\n", - " (-1, '9'),\n", - " (1, '5'),\n", - " (0, '\\n- dont sucres/of which sugar (g): 2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '.'),\n", - " (-1, '1'),\n", - " (1, '2'),\n", - " (0, '\\nProtéines/Proteins (g): 8.0\\nSel/Salt (g): 0.4'),\n", - " (-1, '8'),\n", - " (1, '5\\n'),\n", - " (0,\n", - " \"\\nA conserver à -18°C : Ne jamais recongeler un produit décongelé\\nStore at -18°C: Don't refreeze, once defrosted\\n\"),\n", - " (-1, 'Fabriq'),\n", - " (1, '\\nCo'),\n", - " (0, 'u'),\n", - " (-1, 'é'),\n", - " (1, 'p'),\n", - " (-1, 'en France - Ma'),\n", - " (0, 'de '),\n", - " (-1, 'in Franc'),\n", - " (1, 'pât'),\n", - " (0, 'e'),\n", - " (1, 's'),\n", - " (0,\n", - " '\\n50 CREPES FINES SUCREES AU RHUM NEGRITA® (PLIEES EN QUATRE) D270 55g\\n50 Crêpes fines sucrées au rhum cuites, surgelées -'),\n", - " (0, '50 Crêpes sweetened with rum, baked, frozen\\n'),\n", - " (0, 'N° DE LOT / BATCH : '),\n", - " (-1, '08'),\n", - " (1, '1162'),\n", - " (0, '4'),\n", - " (-1, '20'),\n", - " (0, '1 1'),\n", - " (-1, '5'),\n", - " (1, '3'),\n", - " (0, ':'),\n", - " (-1, '4'),\n", - " (1, '1'),\n", - " (0, '7\\nA consommer de préférence avant le / Best before : 2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '/'),\n", - " (1, '1'),\n", - " (0, '0'),\n", - " (-1, '9'),\n", - " (0, '/202'),\n", - " (-1, '1'),\n", - " (1, '5\\n'),\n", - " (0, '\\n25514\\n'),\n", - " (1, 'FAB : A04A\\n\\n'),\n", - " (0, '(01)03604380255141(15)2'),\n", - " (1, '5'),\n", - " (0, '10'),\n", - " (-1, '9'),\n", - " (0, '2'),\n", - " (-1, '4'),\n", - " (1, '5'),\n", - " (0, '(10)'),\n", - " (-1, '08'),\n", - " (1, '1162'),\n", - " (0, '4'),\n", - " (-1, '20'),\n", - " (0, '1'),\n", - " (0, '(91)0316'),\n", - " (-1, '4'),\n", - " (1, '1'),\n", - " (0, '7'),\n", - " (-1, '6'),\n", - " (1, '5'),\n", - " (0, '\\nEAN N'),\n", - " (-1, '°'),\n", - " (1, 'o'),\n", - " (0, ': 03604380255141'),\n", - " (-1, ' FAB : 00001 '),\n", - " (0, 'Poids net / Net weight : 2750 g\\n'),\n", - " (1, '\\nC.I: 7142 '),\n", - " (0, 'COUP DE PATES'),\n", - " (-1, '®'),\n", - " (0, ' S.A.S'),\n", - " (-1, ' -'),\n", - " (1, '.'),\n", - " (0, ' ZAC DU BEL AIR - 14-16 AVENUE JOSEPH PAXTON - FERRIERES EN BRIE - 77'),\n", - " (-1, '6'),\n", - " (0, '1'),\n", - " (1, '6'),\n", - " (0, '4 MARNE LA VALLEE CEDEX 3'),\n", - " (1, ' - FRANCE')]" - ] - }, - "execution_count": 166, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cleaned_diff" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/backend/worker/diff-assistant/pyproject.toml b/backend/worker/diff-assistant/pyproject.toml deleted file mode 100644 index 01ee4317c..000000000 --- a/backend/worker/diff-assistant/pyproject.toml +++ /dev/null @@ -1,53 +0,0 @@ -[project] -name = "quivr-diff-assistant" -version = "0.1.0" -description = "Diff Assistant" -authors = [ - { name = "Stan Girard", email = "stan@quivr.app" } -] - -dependencies = [ - "python-doctr>=0.9.0", - "matplotlib>=3.9.2", - "mplcursors>=0.5.3", - "diff-match-patch>=20230430", - "scikit-learn>=1.5.1", - "numpy>=1.16.0", - "unstructured>=0.15.9", - "python-magic>=0.4.27", - "pypdfium2>=4.30.0", - "numba>=0.60.0", - "docx2txt>=0.8", - "openpyxl>=3.1.5", - "faiss-cpu>=1.8.0.post1", - "llama-index>=0.11.8", - "openai>=1.44.1", - "pandas>=2.2.2", - "pypdf>=4.3.1", - "llama-index-readers-file>=0.2.1", - "llama-index-llms-openai>=0.2.3", - "python-dotenv>=1.0.1", - "langchain>=0.2.16", - "langchain-openai>=0.1.24", - "opencv-python>=4.10.0.84", - "megaparse>=0.0.31", - "h5py==3.10.0", -] -readme = "README.md" -requires-python = ">= 3.8" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.rye] -managed = true -dev-dependencies = [ - "pytest>=8.3.2", -] - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["quivr_diff_assistant"] diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/__init__.py b/backend/worker/diff-assistant/quivr_diff_assistant/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/main_uc2.py b/backend/worker/diff-assistant/quivr_diff_assistant/main_uc2.py deleted file mode 100644 index 6de22fd92..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/main_uc2.py +++ /dev/null @@ -1,221 +0,0 @@ -import asyncio -from enum import Enum - -import pandas as pd -import streamlit as st -from dotenv import load_dotenv -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.output_parsers import StrOutputParser -from langchain_openai import ChatOpenAI -from llama_index.core import SimpleDirectoryReader, VectorStoreIndex -from llama_index.core.node_parser import UnstructuredElementNodeParser -from llama_index.core.query_engine import RetrieverQueryEngine -from llama_index.core.retrievers import RecursiveRetriever -from llama_index.core.schema import Document -from llama_index.llms.openai import OpenAI -from utils.utils import COMPARISON_PROMPT - -from quivr_diff_assistant.use_case_3.parser import DeadlyParser - -load_dotenv() - -# Set pandas display options -pd.set_option("display.max_rows", None) -pd.set_option("display.max_columns", None) -pd.set_option("display.width", None) -pd.set_option("display.max_colwidth", None) - - -def load_and_process_document(file_path, pickle_file): - print(file_path) - reader = SimpleDirectoryReader(input_files=[file_path]) - docs = reader.load_data() - print(len(docs), " and", len(docs[0].text)) - if len(docs) == 1 and len(docs[0].text) < 9: - print("No text found with classical parse, switching to OCR ...") - parser = DeadlyParser() - doc = parser.deep_parse(file_path) - docs = [Document().from_langchain_format(doc)] - - node_parser = UnstructuredElementNodeParser() - - raw_nodes = node_parser.get_nodes_from_documents(docs) - - base_nodes, node_mappings = node_parser.get_base_nodes_and_mappings(raw_nodes) - return base_nodes, node_mappings - - -def create_query_engine(base_nodes, node_mappings): - vector_index = VectorStoreIndex(base_nodes) - vector_retriever = vector_index.as_retriever(similarity_top_k=5) - recursive_retriever = RecursiveRetriever( - "vector", - retriever_dict={"vector": vector_retriever}, - node_dict=node_mappings, - verbose=True, - ) - return RetrieverQueryEngine.from_args( - recursive_retriever, llm=OpenAI(temperature=0, model="gpt-4") - ) - - -def compare_responses(response1, response2): - llm = OpenAI(temperature=0, model="gpt-4") - prompt = f""" - Compare the following two responses and determine if they convey the same information: - Response for document 1: {response1} - Response for document 2: {response2} - Are these responses essentially the same? Provide a brief explanation for your conclusion. The difference in format are not important, focus on the content and the numbers. - If there are any specific differences, please highlight them with bullet points. Respond in french and in a markdown format. - """ - return llm.complete(prompt) - - -class ComparisonTypes(str, Enum): - CDC_ETIQUETTE = "Cahier des Charges - Etiquette" - CDC_FICHE_DEV = "Cahier des Charges - Fiche Dev" - - -def llm_comparator( - document: str, cdc: str, llm: BaseChatModel, comparison_type: ComparisonTypes -): - chain = COMPARISON_PROMPT | llm | StrOutputParser() - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - text_1 = "Etiquette" - elif comparison_type == ComparisonTypes.CDC_FICHE_DEV: - text_1 = "Fiche Dev" - - return chain.stream( - { - "document": document, - "text_1": text_1, - "cdc": cdc, - "text_2": "Cahier des Charges", - } - ) - - -async def test_main(): - cdc_doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-2-1_Mendiant Lait_QD PC F03 - FR Cahier des charges produit -rev 2021-v2.pdf" - doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-2-1_Proposition eÌtiquette Mendiant Lait croustillant.pdf" - - cdc_doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-1-3_12_CDC_70690_Entremets rond vanille peÌcan individuel_2024.06.28 VALIDE.docx" - doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-1-3_CDP_R&D_TABL_01_Fiche deÌveloppement produit - Entremets vanille peÌcan 28 06 2024.xlsx" - - comparison_type = ComparisonTypes.CDC_FICHE_DEV - - llm = ChatOpenAI( - model="gpt-4o", - temperature=0.1, - max_tokens=None, - max_retries=2, - ) - - parser = DeadlyParser() - parsed_cdc_doc = await parser.aparse(cdc_doc) - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - parsed_doc = await parser.deep_aparse(doc, llm=llm) - else: - parsed_doc = await parser.aparse(doc) - - print("\n\n Cahier des Charges") - print(parsed_cdc_doc.page_content) - - print("\n\n Other document") - print(parsed_doc.page_content) - - comparison = llm_comparator( - document=parsed_doc.page_content, - cdc=parsed_cdc_doc.page_content, - llm=llm, - comparison_type=comparison_type, - ) - - print("\n\n Comparison") - print(comparison) - - -def get_document_path(doc): - try: - with open(doc.name, "wb") as temp_file: - temp_file.write(doc.getbuffer()) - path = temp_file.name - except: - path = doc - - return path - - -async def parse_documents(cdc_doc, doc, comparison_type: ComparisonTypes, llm): - parser = DeadlyParser() - - # Schedule the coroutines as tasks - cdc_task = asyncio.create_task(parser.aparse(get_document_path(cdc_doc))) - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - doc_task = asyncio.create_task( - parser.deep_aparse(get_document_path(doc), llm=llm) - ) - else: - doc_task = asyncio.create_task(parser.aparse(get_document_path(doc))) - - # Optionally, do other work here while tasks are running - - # Await the tasks to get the results - parsed_cdc_doc = await cdc_task - print("\n\n Cahier de Charges: \n", parsed_cdc_doc.page_content) - - parsed_doc = await doc_task - print("\n\n Other doc: \n", parsed_doc.page_content) - - return parsed_cdc_doc, parsed_doc - - -def main(): - st.title("Document Comparison Tool : Use Case 2") - - # File uploaders for two documents - cdc_doc = st.file_uploader( - "Upload Cahier des Charges", type=["docx", "xlsx", "pdf", "txt"] - ) - doc = st.file_uploader( - "Upload Etiquette / Fiche Dev", type=["docx", "xlsx", "pdf", "txt"] - ) - - comparison_type = st.selectbox( - "Select document types", - [ComparisonTypes.CDC_ETIQUETTE.value, ComparisonTypes.CDC_FICHE_DEV.value], - ) - - if st.button("Process Documents and Questions"): - if not cdc_doc or not doc: - st.error("Please upload both documents before launching the processing.") - return - - with st.spinner("Processing files..."): - llm = ChatOpenAI( - model="gpt-4o", - temperature=0.1, - max_tokens=None, - max_retries=2, - ) - - parsed_cdc_doc, parsed_doc = asyncio.run( - parse_documents(cdc_doc, doc, comparison_type=comparison_type, llm=llm) - ) - - comparison = llm_comparator( - document=parsed_doc.page_content, - cdc=parsed_cdc_doc.page_content, - llm=llm, - comparison_type=comparison_type, - ) - # Run the async function using asyncio.run() - # comparison = asyncio.run(process_documents(cdc_doc, doc, comparison_type)) - st.write_stream(comparison) - - -if __name__ == "__main__": - main() diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/main_uc3.py b/backend/worker/diff-assistant/quivr_diff_assistant/main_uc3.py deleted file mode 100644 index c406a6b87..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/main_uc3.py +++ /dev/null @@ -1,125 +0,0 @@ -import asyncio -import os -import tempfile -from enum import Enum -from pathlib import Path - -import streamlit as st -from diff_match_patch import diff_match_patch - -# get environment variables -from dotenv import load_dotenv -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_openai import ChatOpenAI -from use_case_3.diff_type import DiffResult, llm_comparator -from use_case_3.llm_reporter import redact_report -from use_case_3.parser import DeadlyParser - -load_dotenv() - - -class DocumentType(Enum): - ETIQUETTE = "etiquette" - CAHIER_DES_CHARGES = "cdc" - - -async def create_modification_report( - before_file: str | Path, - after_file: str | Path, - type: DocumentType, - llm: BaseChatModel, - partition: bool = False, - use_llm_comparator: bool = False, - parser=DeadlyParser(), -) -> str: - if type == DocumentType.ETIQUETTE: - print("parsing before file") - before_text = parser.deep_parse(before_file, partition=partition, llm=llm) - print("parsing after file") - after_text = parser.deep_parse(after_file, partition=partition, llm=llm) - elif type == DocumentType.CAHIER_DES_CHARGES: - before_text = await parser.aparse(before_file) - after_text = await parser.aparse(after_file) - - print(before_text.page_content) - print(after_text.page_content) - text_after_sections = before_text.page_content.split("\n# ") - text_before_sections = after_text.page_content.split("\n# ") - assert len(text_after_sections) == len(text_before_sections) - - if use_llm_comparator: - print("using llm comparator") - return llm_comparator( - before_text.page_content, after_text.page_content, llm=llm - ) - print("using diff match patch") - dmp = diff_match_patch() - section_diffs = [] - for after_section, before_section in zip( - text_after_sections, text_before_sections, strict=False - ): - main_diff: list[tuple[int, str]] = dmp.diff_main(after_section, before_section) - section_diffs.append(DiffResult(main_diff)) - - return redact_report(section_diffs, llm=llm) - - -def save_uploaded_file(uploaded_file): - with tempfile.NamedTemporaryFile( - delete=False, suffix=os.path.splitext(uploaded_file.name)[1] - ) as tmp_file: - tmp_file.write(uploaded_file.getvalue()) - return tmp_file.name - - -st.title("Document Modification Report Generator : Use Case 3") - -# File uploaders -before_file = st.file_uploader("Upload 'Before' file", type=["pdf", "docx"]) -after_file = st.file_uploader("Upload 'After' file", type=["pdf", "docx"]) - -# Document type selector -doc_type = st.selectbox("Select document type", ["ETIQUETTE", "CAHIER_DES_CHARGES"]) - -# Complexity of document -complexity = st.checkbox("Complex document (lot of text of OCRise)") - -# Process button -if st.button("Process"): - if before_file and after_file: - with st.spinner("Processing files..."): - # Save uploaded files - before_path = save_uploaded_file(before_file) - after_path = save_uploaded_file(after_file) - - # Initialize LLM - openai_gpt4o = ChatOpenAI( - model="gpt-4o", - temperature=0, - max_tokens=None, - max_retries=2, - ) - use_llm_comparator = True if doc_type == "ETIQUETTE" else False - - # Generate report - print("generating report") - report = asyncio.run( - create_modification_report( - before_path, - after_path, - DocumentType[doc_type], - openai_gpt4o, - partition=complexity, - use_llm_comparator=use_llm_comparator, - ) - ) - print("report generated") - # Display results - st.subheader("Modification Report") - st.write(report) - - # Clean up temporary files - os.unlink(before_path) - os.unlink(after_path) - else: - st.error("Please upload both 'Before' and 'After' files.") diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_2/with_quivr_core.py b/backend/worker/diff-assistant/quivr_diff_assistant/use_case_2/with_quivr_core.py deleted file mode 100644 index 6f17414f8..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_2/with_quivr_core.py +++ /dev/null @@ -1,59 +0,0 @@ -# from langchain_openai import OpenAIEmbeddings -# from rich.console import Console -# from rich.panel import Panel -# from rich.prompt import Prompt - -# from quivr_core import Brain -# from quivr_core.config import LLMEndpointConfig -# from quivr_core.llm.llm_endpoint import LLMEndpoint -# from quivr_core.quivr_rag import QuivrQARAG - - -# if __name__ == "__main__": -# brain_1 = Brain.from_files( -# name="cdc_brain", -# file_paths=["data/cdc/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.docx"], -# llm=LLMEndpoint.from_config( -# LLMEndpointConfig(model="gpt-4o-mini", temperature=0.0) -# ), -# embedder=OpenAIEmbeddings(), -# ) - -# brain_2 = Brain.from_files( -# name="etiquette_brain", -# file_paths=[ -# "data/fiche_dev_produit/Cas2-1-3_Entremets_rond_vanille_pecan_individuel.xlsx" -# ], -# llm=LLMEndpoint.from_config( -# LLMEndpointConfig(model="gpt-4o-mini", temperature=0.0) -# ), -# embedder=OpenAIEmbeddings(), -# ) - -# # Check brain info -# brain_1.print_info() -# brain_2.print_info() - -# console = Console() -# console.print(Panel.fit("Ask what to compare : ", style="bold magenta")) - -# while True: -# # Get user input -# section = Prompt.ask("[bold cyan]Section[/bold cyan]") - -# # Check if user wants to exit -# if section.lower() == "exit": -# console.print(Panel("Goodbye!", style="bold yellow")) -# break - -# question = ( -# f"Quelle est/sont le(s) {section} ? Answer only with exact text citation." -# ) -# response_1 = brain_1.ask(question) -# response_2 = brain_2.ask(question, rag_pipeline=QuivrQARAG) -# # Print the answer with typing effect -# console.print(f"[bold green]Quivr CDC[/bold green]: {response_1.answer}") -# console.print() -# console.print(f"[bold blue]Quivr Fiche Dev[/bold blue]: {response_2.answer}") - -# console.print("-" * console.width) diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/__init__.py b/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/diff_type.py b/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/diff_type.py deleted file mode 100644 index 86711106b..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/diff_type.py +++ /dev/null @@ -1,109 +0,0 @@ -from typing import List, Tuple - -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.prompts.prompt import PromptTemplate - -DIFF_PROMPT = PromptTemplate.from_template( - template=""" - You need to compare two texts and report all the differences. Your job is to parse these differences and create a clear, concise report. \ - Organize the report by sections and provide a detailed explanation of each difference. \ - Be specific on difference, it will be reviewed and verified by a Quality engineer. - Here are the different sections of the report: - * Dénominations, comprenant: - * dénomination légale: nom du produit tel qu’il est défini par la réglementation, \ - en général cela inclut aussi avec des information sur son état (cuite, cru, gelé ... ) - * dénomination commercial: nom du produit tel qu’il est vendu au consommateur - * Ingrédients et allergènes en français, comprenant: - * liste d’ingrédients - * traces d’allergènes - * Une sous-section pour chaque sous produit si il y a lieu; - * Ingrédients et allergènes en anglais, comprenant: - * liste d’ingrédients - * traces d’allergènes - * Une sous-section pour chaque sous produit si il y a lieu; - * Eléments de traçabilité, comprenant: - * le code-barre EAN - * le code article - * DDM - date de durabilité minimale - * numéro de lot - * date de fabrication - * adresse de l'entreprise - * Conseils d’utilisation / de manipulation produit, comprenant : - * Conditions de remise en oeuvre - * Durée de vie - * Conditions de transport - * Conditions de conservation : « A conserver à -18°C / Ne pas recongeler un produit décongeler » - * Temps de decongelation - * Temperature de prechauffage - * Poids du produit - * Valeurs / informations nutritionnelles - * Autres - - Notes: - -> Coup de Pates: Tradition & Innovation, est l'entreprise productrice / marque du produit. - - Chaque sections doivent être organisées comme suit et séparées par des lignes entre chaque avant et après: - - ## section_name - - **Avant** : ... - - **Après** : ... - - **Modifications**: - * ... - * ... - - - -----TEXT BEFORE MODIFICATION----- - {before_text} - -----TEXT AFTER MODIFICATION----- - {after_text} - - The report should be written in a professional and formal tone and in French. - """ -) - - -class DiffResult: - def __init__(self, diffs: List[Tuple[int, str]]) -> None: - self.diffs = diffs - - def remove_dummy_diffs(self) -> None: - cleaned_diff = [] - for cat, content in self.diffs: - if content.strip() and content != "\n": - cleaned_diff.append((cat, content)) - - self.diffs = cleaned_diff - - def format_diffs(self) -> str: - text_modified = "" - - sub_stack = 0 - for op, data in self.diffs: - if op == 0: - text_modified += data if sub_stack == 0 else f"_]] {data}" - elif op == -1: - if sub_stack == 0: - text_modified += f"[[{data}->" - sub_stack += 1 - else: - text_modified += f"{data}->" - elif op == 1: - if sub_stack > 0: - text_modified += f"{data}]]" - sub_stack -= 1 - else: - text_modified += f"[[ _ ->{data}]]" - - return text_modified - - def __str__(self) -> str: - return self.format_diffs() - - -def llm_comparator(before_text: str, after_text: str, llm: BaseChatModel) -> str: - chain = DIFF_PROMPT | llm - result = chain.invoke({"before_text": before_text, "after_text": after_text}) - return str(result.content) diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/llm_reporter.py b/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/llm_reporter.py deleted file mode 100644 index abae58afe..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/llm_reporter.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import List - -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.prompts.prompt import PromptTemplate - -from quivr_diff_assistant.use_case_3.diff_type import DiffResult - -REPORT_PROMPT = PromptTemplate.from_template( - template="""You are tasked with analyzing and reporting differences in text for a Quality engineer. The input text contains differences marked with special tokens. Your job is to parse these differences and create a clear, concise report. - - Here is the text containing the differences: - - - {text_modified} - - - RULE #1 : If there are no [[->]] tokens, it indicates no changes to report, inventing changes means death. - The differences are marked using the following format: - - [[before->after]] indicates a change from "before" to "after" - - If there is no "before" text, it indicates an addition - - If there is no "after" text, it indicates a deletion - - If there is no [[ ]] token, it indicates no changes to report - - Make sense of the difference and do not keep the '[' in the report. - - "_" alone means empty. - - Follow these steps to create your report: - - 1. Carefully read through the entire text. - 2. Identify each instance of [[ ]] tokens. - 3. For each instance, determine the modification that was made. - Present your report in the following markdown format: - - # Title (Difference Report) - ## Section Name - ### Subsection Name (if applicable) - * Original: Original text - * Modified: Modified text - * Changes: - * Change 1 - * Change 2 - * Change 3 - - Avoid repetitive infos, only report the changes. - Keep the checkbox when possible and compare the correct check box. - - - Every modification should be clearly stated with the original text and the modified text. - Note that there might be no modifications in some sections. In that case, simply return nothing. - Try to make the report as clear and concise as possible, a point for each modification found with details, avoid big comparisons. - - - Remember, your goal is to create a clear and concise report that allows the Quality engineer to quickly verify the differences. Focus on accuracy and readability in your output, give every indication possible to make it easier to find the modification. - The report should be written in a professional and formal tone and in French.""", -) - - -def redact_report(difference_per_section: List[DiffResult], llm: BaseChatModel) -> str: - report_per_section = [] - combined_diffs = "" - for section in difference_per_section: - if len(section.diffs) == 1 and section.diffs[0][0] == 0: - print("No differences found in this section.") - continue - combined_diffs += str(section) - - chain = REPORT_PROMPT | llm - result = chain.invoke({"text_modified": str(combined_diffs)}) - report_per_section.append(result.content) - - report_text = "" - - for rep in report_per_section: - report_text += "\n".join(rep.split("\n")[1:-1]) + "\n\n" - return report_text diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/parser.py b/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/parser.py deleted file mode 100644 index fb07b9bda..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/use_case_3/parser.py +++ /dev/null @@ -1,288 +0,0 @@ -""" -All of this needs to be in MegaParse, this is just a placeholder for now. -""" - -import base64 -from typing import List - -import cv2 -import numpy as np -from doctr.io import DocumentFile -from doctr.io.elements import Document as doctrDocument -from doctr.models import ocr_predictor -from doctr.models.predictor.pytorch import OCRPredictor -from doctr.utils.common_types import AbstractFile -from langchain_core.documents import Document -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.messages import HumanMessage -from megaparse import MegaParse # FIXME: @chloedia Version problems -from quivr_api.logger import get_logger - -logger = get_logger(__name__) - - -""" -This needs to be in megaparse @chloedia -""" - - -class DeadlyParser: - def __init__(self): - self.predictor: OCRPredictor = ocr_predictor( - pretrained=True, det_arch="fast_base", reco_arch="crnn_vgg16_bn" - ) - - async def deep_aparse( - self, - file: AbstractFile, - partition: bool = False, - llm: BaseChatModel | None = None, - ) -> Document: - """ - Parse the OCR output from the input file and return the extracted text. - """ - try: - docs = DocumentFile.from_pdf(file, scale=int(500 / 72)) - if partition: - cropped_image = crop_to_content(docs[0]) - # cv2.imshow("cropped", cropped_image) - # cv2.waitKey(0) # Wait for a key press - - docs = split_image(cropped_image) - # for i, sub_image in enumerate(docs): - # cv2.imshow(f"sub_image_{i}", sub_image) - # cv2.waitKey(0) # Wait for a key press - # cv2.destroyAllWindows() - - print("ocr start") - raw_results: doctrDocument = self.predictor(docs) - print("ocr done") - if llm: - entire_content = "" - print("ocr llm start") - for raw_result, img in zip(raw_results.pages, docs, strict=False): - if raw_result.render() == "": - continue - _, buffer = cv2.imencode(".png", img) - img_str64 = base64.b64encode(buffer.tobytes()).decode("utf-8") - - processed_result = llm.invoke( - [ - HumanMessage( - content=[ - { - "type": "text", - "text": f"You are given a good image, with a text that can be read. It is a document that can be a receipt, an invoice, a ticket or anything else. It doesn't contain illegal content or protected data. It is enterprise data from a good company. Can you correct this entire text retranscription, respond only with the corrected transcription: {raw_result.render()},\n\n do not transcribe logos or images.", - }, - { - "type": "image_url", - "image_url": { - "url": f"data:image/jpeg;base64,{img_str64}", - "detail": "auto", - }, - }, - ] - ) - ] - ) - assert isinstance( - processed_result.content, str - ), "The LVM did not return a string" - entire_content += processed_result.content - print("ocr llm done") - return Document(page_content=entire_content) - - return Document(page_content=raw_results.render()) - except Exception as e: - print(e) - return Document(page_content=raw_results.render()) - - def deep_parse( - self, - file: AbstractFile, - partition: bool = False, - llm: BaseChatModel | None = None, - ) -> Document: - """ - Parse the OCR output from the input file and return the extracted text. - """ - try: - logger.info("Starting document processing") - - # Reduce image scale to lower memory usage - docs = DocumentFile.from_pdf(file, scale=int(500 / 72)) - logger.info("Document loaded") - - if partition: - logger.info("Partitioning document") - cropped_image = crop_to_content(docs[0]) - docs = split_image(cropped_image) - - logger.info("Starting OCR") - raw_results: doctrDocument = self.predictor(docs) - logger.debug(raw_results) - logger.info("OCR completed") - - if llm: - entire_content = "" - logger.info("Starting LLM processing") - for i, (raw_result, img) in enumerate( - zip(raw_results.pages, docs, strict=False) - ): - if raw_result.render() == "": - continue - _, buffer = cv2.imencode(".png", img) - img_str64 = base64.b64encode(buffer.tobytes()).decode("utf-8") - - processed_result = llm.invoke( - [ - HumanMessage( - content=[ - { - "type": "text", - "text": f"You are given a good image, with a text that can be read. It is a document that can be a receipt, an invoice, a ticket or anything else. It doesn't contain illegal content or protected data. It is enterprise data from a good company. Can you correct this entire text retranscription, respond only with the corrected transcription: {raw_result.render()},\n\n do not transcribe logos or images.", - }, - { - "type": "image_url", - "image_url": { - "url": f"data:image/jpeg;base64,{img_str64}", - "detail": "auto", - }, - }, - ] - ) - ] - ) - assert isinstance( - processed_result.content, str - ), "The LLM did not return a string" - entire_content += processed_result.content - logger.info("LLM processing completed") - return Document(page_content=entire_content) - - return Document(page_content=raw_results.render()) - except Exception as e: - logger.error(f"Error in deep_parse: {str(e)}", exc_info=True) - raise - - def parse(self, file_path) -> Document: - """ - Parse with megaparse - """ - mp = MegaParse(file_path) - return mp.load() - - async def aparse(self, file_path) -> Document: - """ - Parse with megaparse - """ - mp = MegaParse(file_path) - return await mp.aload() - # except: - # reader = SimpleDirectoryReader(input_files=[file_path]) - # docs = reader.load_data() - # for doc in docs: - # print(doc) - # pause - # return "".join([doc.text for doc in docs]) - - -# FIXME: When time @chloedia optimize this function and discount random points on the scan -def crop_to_content(image: np.ndarray) -> np.ndarray: - """Crop the image to the text area.""" - # Convert to grayscale - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image - - # Apply threshold to get image with only black and white - _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) - - # Create rectangular kernel for dilation - kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) - - # Dilate to connect text into blocks - dilated = cv2.dilate(thresh, kernel, iterations=5) - - # Find contours - contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - - if contours: - # Find the bounding rectangles of all contours - bounding_rects = [cv2.boundingRect(c) for c in contours] - - # Combine all bounding rectangles - x = min(rect[0] for rect in bounding_rects) - y = min(rect[1] for rect in bounding_rects) - max_x = max(rect[0] + rect[2] for rect in bounding_rects) - max_y = max(rect[1] + rect[3] for rect in bounding_rects) - w = max_x - x - h = max_y - y - - # Add padding - padding = 10 - x = max(0, x - padding) - y = max(0, y - padding) - w = min(image.shape[1] - x, w + 2 * padding) - h = min(image.shape[0] - y, h + 2 * padding) - - # Crop the image - return image[y : y + h, x : x + w] - else: - return image - - -# FIXME: When time @chloedia optimize this function -def split_image(image: np.ndarray) -> List[np.ndarray]: - """Split the image into 4 parts along the y-axis, avoiding splitting letters.""" - if len(image.shape) == 3: - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - else: - gray = image - - # Apply threshold - _, thresh = cv2.threshold( - gray, 250, 255, cv2.THRESH_BINARY - ) # Adjust threshold for white pixels - - # Find horizontal projection - h_proj = np.sum(thresh, axis=1) - - # Calculate the ideal height for each part - total_height = image.shape[0] - ideal_height = total_height // 4 - - sub_images = [] - start = 0 - - for i in range(3): # We'll make 3 cuts to create 4 parts - target_end = (i + 1) * ideal_height - - # Look for the best cut point around the target end - best_cut = target_end - max_whitespace = 0 - - search_start = max(target_end - ideal_height // 2, 0) - search_end = min(target_end + ideal_height // 2, total_height) - - for j in range(search_start, search_end): - # Check for a continuous white line - if np.all(thresh[j, :] == 255): - whitespace = np.sum( - h_proj[max(0, j - 5) : min(total_height, j + 6)] - == 255 * image.shape[1] - ) - if whitespace > max_whitespace: - max_whitespace = whitespace - best_cut = j - - # If no suitable white line is found, use the target end - if max_whitespace == 0: - best_cut = target_end - - # Make the cut - sub_images.append(image[start:best_cut, :]) - start = best_cut - - # Add the last part - sub_images.append(image[start:, :]) - - return sub_images diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/utils/__init__.py b/backend/worker/diff-assistant/quivr_diff_assistant/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/diff-assistant/quivr_diff_assistant/utils/utils.py b/backend/worker/diff-assistant/quivr_diff_assistant/utils/utils.py deleted file mode 100644 index dd1cfe674..000000000 --- a/backend/worker/diff-assistant/quivr_diff_assistant/utils/utils.py +++ /dev/null @@ -1,91 +0,0 @@ -from langchain_core.prompts.prompt import PromptTemplate - -COMPARISON_PROMPT = PromptTemplate.from_template( - template=""" - You are provided with two texts and . You need to consider the information contained in \ - and compare it with the corresponding information contained in . \ - Keep in mind that contains non-relevant information for this task, and that in you \ - should only focus on the information correspnding to the information contained in . \ - You need to report all the differences between the information contained in and . \\ - Your job is to parse these differences and create a clear, concise report. \ - Organize the report by sections and provide a detailed explanation of each difference. \ - Be specific on difference, it will be reviewed and verified by a highly-trained quality engineer. - Here are the different sections of the report: - * Dénominations, comprenant: - * dénomination légale: nom du produit tel qu’il est défini par la réglementation, \ - en général cela inclut aussi avec des information sur son état (cuite, cru, gelé ... ) - * dénomination commercial: nom du produit tel qu’il est vendu au consommateur - * Ingrédients et allergènes (si presents dans plusieurs langues, comparer langue par langue), comprenant: - * liste d’ingrédients - * traces d’allergènes - * une sous-section pour chaque sous produit si il y a lieu; - * Eléments de traçabilité, comprenant: - * le code-barre EAN - * le code article - * numéro de lot - * date de fabrication - * adresse de l'entreprise - * Conseils d’utilisation / de manipulation produit, comprenant : - * Conditions / conseils de remise en oeuvre - * Durée de vie - * Durée de conservation (à compter de la date de production, à température ambiante / réfrigérée) - * DDM - date de durabilité minimale - * Conditions de transport - * Conditions de conservation : « A conserver à -18°C / Ne pas recongeler un produit décongeler » - * Temps de decongelation - * Temperature de prechauffage - * Caractéristiques / parametres physiques produit (unité de négoce), comprenant: - * poids de la pièce - * dimensions de la pièce - * poids du produit / unité de négoce (typiquement, carton) - * dimensions du produit / unité de négoce (typiquement, carton) - * nombre de pièces par unité de negoce (typiquement, carton) / colis - * poids du colis / carton - * Données palettisation / donnée technique sur palette (unité de transport) - * hauteur palette - * dimensions de l'unité de negoce (typiquement, carton) / colis - * nombre de colis par couche / palette - * Valeurs / informations nutritionnelles - * Autres - - Notes: - -> Coup de Pates: Tradition & Innovation, est l'entreprise productrice / marque du produit. - - Chaque sections doivent être organisées comme suit : - ## Section name - **** : - * ... - * ... - - **** : ... - * ... - * ... - - **Differences**: - * ... - * ... - - - Beginning of - {document} - End of - - - Beginning of - {cdc} - End of - - - You need to consider all the information contained in and compare it \ - with the corresponding information contained in . - The report should be written in a professional and formal tone and in French \ - and it should follow the structure outlined above. If doesn't contain a particular information, \ - then you should ignore that information for as well and avoid reporting any differences. - - In the report you should replace evry occurence of with {text_1} and every occurence of with {text_2}. - - ## Dénominations - **{text_1}** : - * - """ -) diff --git a/backend/worker/diff-assistant/tests/conftest.py b/backend/worker/diff-assistant/tests/conftest.py deleted file mode 100644 index fb82a30e7..000000000 --- a/backend/worker/diff-assistant/tests/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -import pytest - - -@pytest.fixture -def hello_message(): - return "Hello from diff-assistant!" diff --git a/backend/worker/diff-assistant/tests/test_hello.py b/backend/worker/diff-assistant/tests/test_hello.py deleted file mode 100644 index a8fb11758..000000000 --- a/backend/worker/diff-assistant/tests/test_hello.py +++ /dev/null @@ -1,5 +0,0 @@ -from use_case_3 import hello - - -def test_hello(hello_message): - assert hello() == hello_message diff --git a/backend/worker/pyproject.toml b/backend/worker/pyproject.toml deleted file mode 100644 index 92eec4c3c..000000000 --- a/backend/worker/pyproject.toml +++ /dev/null @@ -1,55 +0,0 @@ -[project] -name = "quivr-worker" -version = "0.1.0" -description = "Add your description here" -authors = [ - { name = "Stan Girard", email = "stan@quivr.app" } -] -dependencies = [ - "quivr-core[all,megaparse]", - "quivr-api", - "quivr-diff-assistant", - "celery[redis]>=5.0.0", - "python-dotenv>=1.0.0", - "playwright>=1.0.0", - "openai>=1.0.0", - "flower>=2.0.1", - "torch==2.4.0; platform_machine != 'x86_64'", - "torch==2.4.0+cpu; platform_machine == 'x86_64'", - "torchvision==0.19.0; platform_machine != 'x86_64'", - "torchvision==0.19.0+cpu; platform_machine == 'x86_64'", - "fpdf2>=2.7.9", -] -readme = "README.md" -requires-python = ">= 3.11" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.rye] -managed = true -dev-dependencies = [ -] - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["quivr_worker"] - -[[tool.rye.sources]] -name = "pytorch" -url = "https://download.pytorch.org/whl/cpu" - -[[tool.rye.sources]] -name = "quivr-core" -path = "../quivr-core" - -[[tool.rye.sources]] -name = "quivr-api" -path = "../quivr-api" - -[[tool.rye.sources]] -name = "quivr-diff-assistant" -path = "./diff-assistant" diff --git a/backend/worker/quivr_worker/__init__.py b/backend/worker/quivr_worker/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/assistants/__init__.py b/backend/worker/quivr_worker/assistants/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/assistants/assistants.py b/backend/worker/quivr_worker/assistants/assistants.py deleted file mode 100644 index 1571072bb..000000000 --- a/backend/worker/quivr_worker/assistants/assistants.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.upload.service.upload_file import ( - upload_file_storage, -) - -from quivr_worker.assistants.cdp_use_case_2 import process_cdp_use_case_2 -from quivr_worker.assistants.cdp_use_case_3 import process_cdp_use_case_3 -from quivr_worker.utils.pdf_generator.pdf_generator import PDFGenerator, PDFModel - - -async def process_assistant( - assistant_id: str, - notification_uuid: str, - task_id: int, - tasks_service: TasksService, - user_id: str, -): - print(task_id) - task = await tasks_service.get_task_by_id(task_id, user_id) # type: ignore - assistant_name = task.assistant_name - output = "" - if assistant_id == 3: - output = await process_cdp_use_case_3( - assistant_id, notification_uuid, task_id, tasks_service, user_id - ) - elif assistant_id == 2: - output = await process_cdp_use_case_2( - assistant_id, notification_uuid, task_id, tasks_service, user_id - ) - else: - new_task = await tasks_service.update_task(task_id, {"status": "processing"}) - # Add a random delay of 10 to 20 seconds - - task_result = {"status": "completed", "answer": output} - - output_dir = f"{assistant_id}/{notification_uuid}" - os.makedirs(output_dir, exist_ok=True) - output_path = f"{output_dir}/output.pdf" - - generated_pdf = PDFGenerator(PDFModel(title=assistant_name, content=output)) - generated_pdf.print_pdf() - generated_pdf.output(output_path) - - with open(output_path, "rb") as file: - await upload_file_storage(file, output_path) - - # Now delete the file - os.remove(output_path) - await tasks_service.update_task(task_id, task_result) diff --git a/backend/worker/quivr_worker/assistants/cdp_use_case_2.py b/backend/worker/quivr_worker/assistants/cdp_use_case_2.py deleted file mode 100644 index 275dc1ae5..000000000 --- a/backend/worker/quivr_worker/assistants/cdp_use_case_2.py +++ /dev/null @@ -1,313 +0,0 @@ -import random -import string -from enum import Enum - -import pandas as pd - -# get environment variables -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.output_parsers import StrOutputParser -from langchain_openai import ChatOpenAI -from llama_index.core import SimpleDirectoryReader, VectorStoreIndex -from llama_index.core.node_parser import UnstructuredElementNodeParser -from llama_index.core.query_engine import RetrieverQueryEngine -from llama_index.core.retrievers import RecursiveRetriever -from llama_index.core.schema import Document -from llama_index.llms.openai import OpenAI -from quivr_api.logger import get_logger -from quivr_api.modules.assistant.dto.inputs import InputAssistant -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.dependencies import get_supabase_client -from quivr_diff_assistant.use_case_3.parser import DeadlyParser -from quivr_diff_assistant.utils.utils import COMPARISON_PROMPT - -logger = get_logger(__name__) - -# Set pandas display options -pd.set_option("display.max_rows", None) -pd.set_option("display.max_columns", None) -pd.set_option("display.width", None) -pd.set_option("display.max_colwidth", None) - - -def load_and_process_document(file_path, pickle_file): - print(file_path) - reader = SimpleDirectoryReader(input_files=[file_path]) - docs = reader.load_data() - print(len(docs), " and", len(docs[0].text)) - if len(docs) == 1 and len(docs[0].text) < 9: - print("No text found with classical parse, switching to OCR ...") - parser = DeadlyParser() - doc = parser.deep_parse(file_path) - docs = [Document().from_langchain_format(doc)] - - node_parser = UnstructuredElementNodeParser() - - raw_nodes = node_parser.get_nodes_from_documents(docs) - - base_nodes, node_mappings = node_parser.get_base_nodes_and_mappings(raw_nodes) - return base_nodes, node_mappings - - -def create_query_engine(base_nodes, node_mappings): - vector_index = VectorStoreIndex(base_nodes) - vector_retriever = vector_index.as_retriever(similarity_top_k=5) - recursive_retriever = RecursiveRetriever( - "vector", - retriever_dict={"vector": vector_retriever}, - node_dict=node_mappings, - verbose=True, - ) - return RetrieverQueryEngine.from_args( - recursive_retriever, llm=OpenAI(temperature=0, model="gpt-4") - ) - - -def compare_responses(response1, response2): - llm = OpenAI(temperature=0, model="gpt-4") - prompt = f""" - Compare the following two responses and determine if they convey the same information: - Response for document 1: {response1} - Response for document 2: {response2} - Are these responses essentially the same? Provide a brief explanation for your conclusion. The difference in format are not important, focus on the content and the numbers. - If there are any specific differences, please highlight them with bullet points. Respond in french and in a markdown format. - """ - return llm.complete(prompt) - - -class ComparisonTypes(str, Enum): - CDC_ETIQUETTE = "Cahier des Charges - Etiquette" - CDC_FICHE_DEV = "Cahier des Charges - Fiche Dev" - - -def llm_comparator( - document: str, cdc: str, llm: BaseChatModel, comparison_type: ComparisonTypes -): - chain = COMPARISON_PROMPT | llm | StrOutputParser() - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - text_1 = "Etiquette" - elif comparison_type == ComparisonTypes.CDC_FICHE_DEV: - text_1 = "Fiche Dev" - - return chain.stream( - { - "document": document, - "text_1": text_1, - "cdc": cdc, - "text_2": "Cahier des Charges", - } - ) - - -async def process_cdp_use_case_2( - assistant_id: str, - notification_uuid: str, - task_id: int, - tasks_service: TasksService, - user_id: str, -) -> str: - task = await tasks_service.get_task_by_id(task_id, user_id) # type: ignore - logger.info(f"Task: {task} ðŸ“") - # Parse settings into InputAssistant - input_assistant = InputAssistant.model_validate(task.settings) - assert input_assistant.inputs.files is not None - assert len(input_assistant.inputs.files) == 2 - - # Get the value of the "Document 1" key and "Document 2" key. The input files might not be in the order of "Document 1" and "Document 2" - # So we need to find the correct order - logger.info(f"Input assistant: {input_assistant} 📂") - before_file_key = input_assistant.inputs.files[0].key - after_file_key = input_assistant.inputs.files[1].key - - before_file_value = input_assistant.inputs.files[0].value - after_file_value = input_assistant.inputs.files[1].value - - if before_file_key == "Document 2": - before_file_value = input_assistant.inputs.files[1].value - after_file_value = input_assistant.inputs.files[0].value - - # Get the files from supabase - supabase_client = get_supabase_client() - path = f"{task.assistant_id}/{task.pretty_id}/" - logger.info(f"Path: {path} ðŸ“") - - await tasks_service.update_task(task_id, {"status": "processing"}) - - before_file_data = supabase_client.storage.from_("quivr").download( - f"{path}{before_file_value}" - ) - after_file_data = supabase_client.storage.from_("quivr").download( - f"{path}{after_file_value}" - ) - - # Generate a random string of 8 characters - random_string = "".join(random.choices(string.ascii_letters + string.digits, k=8)) - - # Write temp files with the original name without using save_uploaded_file - # because the file is already in the quivr bucket - before_file_path = f"/tmp/{random_string}_{before_file_value}" - after_file_path = f"/tmp/{random_string}_{after_file_value}" - with open(before_file_path, "wb") as f: - f.write(before_file_data) - with open(after_file_path, "wb") as f: - f.write(after_file_data) - assert input_assistant.inputs.select_texts is not None - value_use_case = input_assistant.inputs.select_texts[0].value - - ## Get the document type - document_type = None - if value_use_case == "Cahier des charges VS Etiquettes": - document_type = ComparisonTypes.CDC_ETIQUETTE - elif value_use_case == "Fiche Dev VS Cahier des charges": - document_type = ComparisonTypes.CDC_FICHE_DEV - else: - logger.error(f"⌠Document type not supported: {value_use_case}") - raise ValueError(f"Document type not supported: {value_use_case}") - parser = DeadlyParser() - logger.info(f"Document type: {document_type} 📄") - llm = ChatOpenAI( - model="gpt-4o", - temperature=0.1, - max_tokens=None, - max_retries=2, - ) - - before_file_parsed = await parser.aparse(before_file_path) - logger.info("Before file parsed 📜") - after_file_parsed = None - if document_type == ComparisonTypes.CDC_ETIQUETTE: - logger.info("Parsing after file with deep parse ðŸ”") - after_file_parsed = await parser.deep_aparse(after_file_path, llm=llm) - else: - logger.info("Parsing after file with classical parse ðŸ”") - after_file_parsed = await parser.aparse(after_file_path) - - logger.info("Comparing documents âš–ï¸") - comparison = llm_comparator( - document=after_file_parsed.page_content, - cdc=before_file_parsed.page_content, - llm=llm, - comparison_type=document_type, - ) - - logger.info(f"Comparison: {comparison} ✅") - return "".join(comparison) - - -async def test_main(): - cdc_doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-2-1_Mendiant Lait_QD PC F03 - FR Cahier des charges produit -rev 2021-v2.pdf" - doc = "/Users/jchevall/Coding/diff-assistant/data/Use case #2/Cas2-2-1_Proposition eÌtiquette Mendiant Lait croustillant.pdf" - - comparison_type = ComparisonTypes.CDC_FICHE_DEV - - llm = ChatOpenAI( - model="gpt-4o", - temperature=0.1, - max_tokens=None, - max_retries=2, - ) - - parser = DeadlyParser() - parsed_cdc_doc = await parser.aparse(cdc_doc) - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - parsed_doc = await parser.deep_aparse(doc, llm=llm) - else: - parsed_doc = await parser.aparse(doc) - - print("\n\n Cahier des Charges") - print(parsed_cdc_doc.page_content) - - print("\n\n Other document") - print(parsed_doc.page_content) - - comparison = llm_comparator( - document=parsed_doc.page_content, - cdc=parsed_cdc_doc.page_content, - llm=llm, - comparison_type=comparison_type, - ) - - print("\n\n Comparison") - print(comparison) - - -def get_document_path(doc): - try: - with open(doc.name, "wb") as temp_file: - temp_file.write(doc.getbuffer()) - path = temp_file.name - except: - path = doc - - return path - - -async def parse_documents(cdc_doc, doc, comparison_type: ComparisonTypes, llm): - parser = DeadlyParser() - - # Schedule the coroutines as tasks - cdc_task = asyncio.create_task(parser.aparse(get_document_path(cdc_doc))) - - if comparison_type == ComparisonTypes.CDC_ETIQUETTE: - doc_task = asyncio.create_task( - parser.deep_aparse(get_document_path(doc), llm=llm) - ) - else: - doc_task = asyncio.create_task(parser.aparse(get_document_path(doc))) - - # Optionally, do other work here while tasks are running - - # Await the tasks to get the results - parsed_cdc_doc = await cdc_task - print("\n\n Cahier de Charges: \n", parsed_cdc_doc.page_content) - - parsed_doc = await doc_task - print("\n\n Other doc: \n", parsed_doc.page_content) - - return parsed_cdc_doc, parsed_doc - - -# def main(): -# st.title("Document Comparison Tool : Use Case 2") - -# # File uploaders for two documents -# cdc_doc = st.file_uploader( -# "Upload Cahier des Charges", type=["docx", "xlsx", "pdf", "txt"] -# ) -# doc = st.file_uploader( -# "Upload Etiquette / Fiche Dev", type=["docx", "xlsx", "pdf", "txt"] -# ) - -# comparison_type = st.selectbox( -# "Select document types", -# [ComparisonTypes.CDC_ETIQUETTE.value, ComparisonTypes.CDC_FICHE_DEV.value], -# ) - -# if st.button("Process Documents and Questions"): -# if not cdc_doc or not doc: -# st.error("Please upload both documents before launching the processing.") -# return - -# with st.spinner("Processing files..."): -# llm = ChatOpenAI( -# model="gpt-4o", -# temperature=0.1, -# max_tokens=None, -# max_retries=2, -# ) - -# parsed_cdc_doc, parsed_doc = asyncio.run( -# parse_documents(cdc_doc, doc, comparison_type=comparison_type, llm=llm) -# ) - -# comparison = llm_comparator( -# document=parsed_doc.page_content, -# cdc=parsed_cdc_doc.page_content, -# llm=llm, -# comparison_type=comparison_type, -# ) -# # Run the async function using asyncio.run() -# # comparison = asyncio.run(process_documents(cdc_doc, doc, comparison_type)) -# st.write_stream(comparison) diff --git a/backend/worker/quivr_worker/assistants/cdp_use_case_3.py b/backend/worker/quivr_worker/assistants/cdp_use_case_3.py deleted file mode 100644 index 924ff13b9..000000000 --- a/backend/worker/quivr_worker/assistants/cdp_use_case_3.py +++ /dev/null @@ -1,223 +0,0 @@ -import os -import random -import string -import tempfile -from enum import Enum -from pathlib import Path - -from diff_match_patch import diff_match_patch - -# get environment variables -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_openai import ChatOpenAI -from quivr_api.logger import get_logger -from quivr_api.modules.assistant.dto.inputs import InputAssistant -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.dependencies import get_supabase_client -from quivr_diff_assistant.use_case_3.diff_type import DiffResult, llm_comparator -from quivr_diff_assistant.use_case_3.llm_reporter import redact_report -from quivr_diff_assistant.use_case_3.parser import DeadlyParser - -logger = get_logger(__name__) - - -class DocumentType(Enum): - ETIQUETTE = "etiquette" - CAHIER_DES_CHARGES = "cdc" - - -async def process_cdp_use_case_3( - assistant_id: str, - notification_uuid: str, - task_id: int, - tasks_service: TasksService, - user_id: str, -) -> str: - task = await tasks_service.get_task_by_id(task_id, user_id) # type: ignore - - # Parse settings into InputAssistant - input_assistant = InputAssistant.model_validate(task.settings) - assert input_assistant.inputs.files is not None - assert len(input_assistant.inputs.files) == 2 - - # Get the value of the "Document 1" key and "Document 2" key. The input files might not be in the order of "Document 1" and "Document 2" - # So we need to find the correct order - before_file_key = input_assistant.inputs.files[0].key - after_file_key = input_assistant.inputs.files[1].key - - before_file_value = input_assistant.inputs.files[0].value - after_file_value = input_assistant.inputs.files[1].value - - if before_file_key == "Document 2": - before_file_value = input_assistant.inputs.files[1].value - after_file_value = input_assistant.inputs.files[0].value - - # Get the files from supabase - supabase_client = get_supabase_client() - path = f"{task.assistant_id}/{task.pretty_id}/" - - await tasks_service.update_task(task_id, {"status": "processing"}) - - # Before file key - parsed from the - before_file_data = supabase_client.storage.from_("quivr").download( - f"{path}{before_file_value}" - ) - after_file_data = supabase_client.storage.from_("quivr").download( - f"{path}{after_file_value}" - ) - - # Generate a random string of 8 characters - random_string = "".join(random.choices(string.ascii_letters + string.digits, k=8)) - - # Write temp files with the original name without using save_uploaded_file - # because the file is already in the quivr bucket - before_file_path = f"/tmp/{random_string}_{before_file_value}" - after_file_path = f"/tmp/{random_string}_{after_file_value}" - with open(before_file_path, "wb") as f: - f.write(before_file_data) - with open(after_file_path, "wb") as f: - f.write(after_file_data) - - assert input_assistant.inputs.select_texts is not None - value_use_case = input_assistant.inputs.select_texts[0].value - - ## Get the document type - document_type = None - if value_use_case == "Etiquettes": - document_type = DocumentType.ETIQUETTE - elif value_use_case == "Cahier des charges": - document_type = DocumentType.CAHIER_DES_CHARGES - else: - raise ValueError(f"Invalid value for use case: {value_use_case}") - - ## Get the hard to read document boolean value - assert input_assistant.inputs.booleans is not None - hard_to_read_document = input_assistant.inputs.booleans[0].value - - assert before_file_data is not None - assert after_file_data is not None - - openai_gpt4o = ChatOpenAI( - model="gpt-4o", - temperature=0, - max_tokens=None, - max_retries=2, - ) - - llm_comparator = True if document_type == DocumentType.ETIQUETTE else False - report = await create_modification_report( - before_file=before_file_path, - after_file=after_file_path, - type=document_type, - llm=openai_gpt4o, - partition=hard_to_read_document, - use_llm_comparator=llm_comparator, - ) - - os.unlink(before_file_path) - os.unlink(after_file_path) - return report - - -async def create_modification_report( - before_file: str | Path | bytes, - after_file: str | Path | bytes, - type: DocumentType, - llm: BaseChatModel, - partition: bool = False, - use_llm_comparator: bool = False, - parser=DeadlyParser(), -) -> str: - if type == DocumentType.ETIQUETTE: - logger.debug("parsing before file") - before_text = parser.deep_parse(before_file, partition=partition, llm=llm) - logger.debug("parsing after file") - after_text = parser.deep_parse(after_file, partition=partition, llm=llm) - elif type == DocumentType.CAHIER_DES_CHARGES: - before_text = await parser.aparse(before_file) - after_text = await parser.aparse(after_file) - - logger.debug(before_text.page_content) - logger.debug(after_text.page_content) - text_after_sections = before_text.page_content.split("\n# ") - text_before_sections = after_text.page_content.split("\n# ") - - if use_llm_comparator: - logger.debug("using llm comparator") - llm_comparator_result = llm_comparator( - before_text.page_content, after_text.page_content, llm=llm - ) - return llm_comparator_result - logger.debug("using diff match patch") - dmp = diff_match_patch() - section_diffs = [] - for after_section, before_section in zip( - text_after_sections, text_before_sections, strict=False - ): - main_diff: list[tuple[int, str]] = dmp.diff_main(after_section, before_section) - section_diffs.append(DiffResult(main_diff)) - - logger.debug(section_diffs) - report = redact_report(section_diffs, llm=llm) - return report - - -def save_uploaded_file(uploaded_file): - with tempfile.NamedTemporaryFile( - delete=False, suffix=os.path.splitext(uploaded_file.name)[1] - ) as tmp_file: - tmp_file.write(uploaded_file.getvalue()) - return tmp_file.name - - -# st.title("Document Modification Report Generator : Use Case 3") - -# # File uploaders -# before_file = st.file_uploader("Upload 'Before' file", type=["pdf", "docx"]) -# after_file = st.file_uploader("Upload 'After' file", type=["pdf", "docx"]) - -# # Document type selector -# doc_type = st.selectbox("Select document type", ["ETIQUETTE", "CAHIER_DES_CHARGES"]) - -# # Complexity of document -# complexity = st.checkbox("Complex document (lot of text of OCRise)") - -# # Process button -# if st.button("Process"): -# if before_file and after_file: -# with st.spinner("Processing files..."): -# # Save uploaded files -# before_path = save_uploaded_file(before_file) -# after_path = save_uploaded_file(after_file) - -# # Initialize LLM -# openai_gpt4o = ChatOpenAI( -# model="gpt-4o", -# temperature=0, -# max_tokens=None, -# max_retries=2, -# ) -# use_llm_comparator = True if doc_type == "ETIQUETTE" else False - -# # Generate report -# logger.debug("generating report") -# report = asyncio.run( -# create_modification_report( -# before_path, -# after_path, -# DocumentType[doc_type], -# openai_gpt4o, -# partition=complexity, -# use_llm_comparator=use_llm_comparator, -# ) -# ) -# logger.debug("report generated") -# # Display results -# st.subheader("Modification Report") -# st.write(report) - -# # Clean up temporary files -# os.unlink(before_path) -# os.unlink(after_path) -# else: -# st.error("Please upload both 'Before' and 'After' files.") diff --git a/backend/worker/quivr_worker/celery_monitor.py b/backend/worker/quivr_worker/celery_monitor.py deleted file mode 100644 index f191c17fd..000000000 --- a/backend/worker/quivr_worker/celery_monitor.py +++ /dev/null @@ -1,201 +0,0 @@ -import asyncio -import threading -from enum import Enum -from queue import Queue -from uuid import UUID - -from attr import dataclass -from celery.result import AsyncResult -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger, setup_logger -from quivr_api.modules.dependencies import async_engine -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.assistant.repository.tasks import TasksRepository -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.dto.inputs import NotificationUpdatableProperties -from quivr_api.modules.notification.entity.notification import NotificationsStatusEnum -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_core.models import KnowledgeStatus -from sqlmodel.ext.asyncio.session import AsyncSession - -setup_logger("notifier.log", send_log_server=False) -logger = get_logger("notifier_service") -notification_service = NotificationService() -queue = Queue() - - -class TaskStatus(str, Enum): - FAILED = "task-failed" - SUCCESS = "task-succeeded" - - -class TaskIdentifier(str, Enum): - PROCESS_FILE_TASK = "process_file_task" - PROCESS_CRAWL_TASK = "process_crawl_task" - PROCESS_ASSISTANT_TASK = "process_assistant_task" - - -@dataclass -class TaskEvent: - task_id: str - brain_id: UUID | None - task_name: TaskIdentifier - notification_id: str - knowledge_id: UUID | None - status: TaskStatus - - -async def handler_loop(): - session = AsyncSession(async_engine, expire_on_commit=False, autoflush=False) - knowledge_service = KnowledgeService(KnowledgeRepository(session)) - task_service = TasksService(TasksRepository(session)) - - logger.info("Initialized knowledge_service. Listening to task event...") - while True: - try: - event: TaskEvent = queue.get() - if event.status == TaskStatus.FAILED: - if event.task_name == TaskIdentifier.PROCESS_ASSISTANT_TASK: - # Update the task status to error - logger.info(f"task {event.task_id} process_assistant_task failed. Updating task {event.notification_id} to error") - await task_service.update_task(int(event.notification_id), {"status": "error"}) - else: - logger.error( - f"task {event.task_id} process_file_task. Sending notifition {event.notification_id}" - ) - notification_service.update_notification_by_id( - event.notification_id, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.ERROR, - description=( - "An error occurred while processing the file" - if event.task_name == TaskIdentifier.PROCESS_FILE_TASK - else "An error occurred while processing the URL" - ), - ), - ) - logger.error( - f"task {event.task_id} process_file_task failed. Updating knowledge {event.knowledge_id} to Error" - ) - if event.knowledge_id: - await knowledge_service.update_status_knowledge( - event.knowledge_id, KnowledgeStatus.ERROR - ) - logger.error( - f"task {event.task_id} process_file_task . Updating knowledge {event.knowledge_id} status to Error" - ) - - if event.status == TaskStatus.SUCCESS: - logger.info( - f"task {event.task_id} process_file_task succeeded. Sending notification {event.notification_id}" - ) - notification_service.update_notification_by_id( - event.notification_id, - NotificationUpdatableProperties( - status=NotificationsStatusEnum.SUCCESS, - description=( - "Your file has been properly uploaded!" - if event.task_name == TaskIdentifier.PROCESS_FILE_TASK - else "Your URL has been properly crawled!" - ), - ), - ) - if event.knowledge_id: - await knowledge_service.update_status_knowledge( - knowledge_id=event.knowledge_id, - status=KnowledgeStatus.UPLOADED, - brain_id=event.brain_id, - ) - logger.info( - f"task {event.task_id} process_file_task failed. Updating knowledge {event.knowledge_id} to UPLOADED" - ) - - except Exception as e: - logger.error(f"Excpetion occured handling event {event}: {e}") - - -def notifier(app): - state = app.events.State() - - def handle_task_event(event): - try: - state.event(event) - task = state.tasks.get(event["uuid"]) - task_result = AsyncResult(task.id, app=app) - task_name, task_kwargs = task_result.name, task_result.kwargs - - if task_name == "process_file_task" or task_name == "process_crawl_task": - logger.debug(f"Received Event : {task} - {task_name} {task_kwargs} ") - notification_id = task_kwargs["notification_id"] - knowledge_id = task_kwargs.get("knowledge_id", None) - brain_id = task_kwargs.get("brain_id", None) - event = TaskEvent( - task_id=task, - task_name=TaskIdentifier(task_name), - knowledge_id=knowledge_id, - brain_id=brain_id, - notification_id=notification_id, - status=TaskStatus(event["type"]), - ) - queue.put(event) - elif task_name == "process_assistant_task": - logger.debug(f"Received Event : {task} - {task_name} {task_kwargs} ") - notification_uuid = task_kwargs["notification_uuid"] - task_id = task_kwargs["task_id"] - event = TaskEvent( - task_id=task, - task_name=TaskIdentifier(task_name), - knowledge_id=None, - brain_id=None, - notification_id=task_id, - status=TaskStatus(event["type"]), - ) - queue.put(event) - - except Exception as e: - logger.exception(f"handling event {event} raised exception: {e}") - - with app.connection() as connection: - recv = app.events.Receiver( - connection, - handlers={ - "task-failed": handle_task_event, - "task-succeeded": handle_task_event, - }, - ) - recv.capture(limit=None, timeout=None, wakeup=True) - - -def is_being_executed(task_name: str) -> bool: - """Returns whether the task with given task_name is already being executed. - - Args: - task_name: Name of the task to check if it is running currently. - Returns: A boolean indicating whether the task with the given task name is - running currently. - """ - active_tasks = celery.control.inspect().active() - if not active_tasks: - return False - - for worker, running_tasks in active_tasks.items(): - for task in running_tasks: - if task["name"] == task_name: # type: ignore - return True - - return False - - -if __name__ == "__main__": - logger.info("Started quivr-notifier service...") - - def start_handler(): - asyncio.run(handler_loop()) - - thread = threading.Thread(target=start_handler, daemon=True) - thread.start() - - notifier(celery) diff --git a/backend/worker/quivr_worker/celery_worker.py b/backend/worker/quivr_worker/celery_worker.py deleted file mode 100644 index dce4a301d..000000000 --- a/backend/worker/quivr_worker/celery_worker.py +++ /dev/null @@ -1,435 +0,0 @@ -import asyncio -import os -from uuid import UUID - -import structlog -import torch -from celery import signals -from celery.schedules import crontab -from celery.signals import worker_process_init -from celery.utils.log import get_task_logger -from dotenv import load_dotenv -from quivr_api.celery_config import celery -from quivr_api.logger import setup_logger -from quivr_api.models.settings import settings -from quivr_api.modules.assistant.repository.tasks import TasksRepository -from quivr_api.modules.assistant.services.tasks_service import TasksService -from quivr_api.modules.brain.integrations.Notion.Notion_connector import NotionConnector -from quivr_api.modules.brain.repository.brains_vectors import BrainsVectors -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.dependencies import get_supabase_client -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.repository.storage import SupabaseS3Storage -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.dto.inputs import SyncsUserStatus -from quivr_api.modules.sync.repository.sync_files import SyncFilesRepository -from quivr_api.modules.sync.service.sync_notion import SyncNotionService -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.vector.repository.vectors_repository import VectorRepository -from quivr_api.modules.vector.service.vector_service import VectorService -from quivr_api.utils.telemetry import maybe_send_telemetry -from sqlalchemy import Engine, create_engine -from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine -from sqlmodel import Session, text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_worker.assistants.assistants import process_assistant -from quivr_worker.celery_monitor import is_being_executed -from quivr_worker.check_premium import check_is_premium -from quivr_worker.process.process_s3_file import process_uploaded_file -from quivr_worker.process.process_url import process_url_func -from quivr_worker.syncs.process_active_syncs import ( - SyncServices, - process_all_active_syncs, - process_notion_sync, - process_sync, -) -from quivr_worker.syncs.store_notion import fetch_and_store_notion_files_async -from quivr_worker.utils.utils import _patch_json - -torch.set_num_threads(1) - -setup_logger("worker.log", send_log_server=False) -load_dotenv() - -logger = structlog.wrap_logger(get_task_logger(__name__)) - -_patch_json() - - -# FIXME: load at init time -# Services -supabase_client = get_supabase_client() -# document_vector_store = get_documents_vector_store() -notification_service = NotificationService() -sync_active_service = SyncService() -sync_user_service = SyncUserService() -sync_files_repo_service = SyncFilesRepository() -brain_service = BrainService() -brain_vectors = BrainsVectors() -storage = SupabaseS3Storage() -notion_service: SyncNotionService | None = None -async_engine: AsyncEngine | None = None -engine: Engine | None = None - - -@signals.task_prerun.connect -def on_task_prerun(sender, task_id, task, args, kwargs, **_): - structlog.contextvars.bind_contextvars(task_id=task_id, task_name=task.name) - if vars := kwargs.get("contextvars", None): - structlog.contextvars.bind_contextvars(**vars) - - -@worker_process_init.connect -def init_worker(**kwargs): - global async_engine - global engine - if not async_engine: - async_engine = create_async_engine( - settings.pg_database_async_url, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, - # NOTE: pessimistic bound on - pool_pre_ping=True, - pool_size=10, # NOTE: no bouncer for now, if 6 process workers => 6 - pool_recycle=1800, - ) - - if not engine: - engine = create_engine( - settings.pg_database_url, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, - # NOTE: pessimistic bound on - pool_pre_ping=True, - pool_size=10, # NOTE: no bouncer for now, if 6 process workers => 6 - pool_recycle=1800, - ) - - -@celery.task( - retries=3, - default_retry_delay=1, - name="process_assistant_task", - autoretry_for=(Exception,), -) -def process_assistant_task( - assistant_id: str, - notification_uuid: str, - task_id: int, - user_id: str, -): - logger.info( - f"process_assistant_task started for assistant_id={assistant_id}, notification_uuid={notification_uuid}, task_id={task_id}" - ) - - loop = asyncio.get_event_loop() - loop.run_until_complete( - aprocess_assistant_task( - assistant_id, - notification_uuid, - task_id, - user_id, - ) - ) - - -async def aprocess_assistant_task( - assistant_id: str, - notification_uuid: str, - task_id: int, - user_id: str, -): - global async_engine - assert async_engine - async with AsyncSession(async_engine) as async_session: - try: - await async_session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - tasks_repository = TasksRepository(async_session) - tasks_service = TasksService(tasks_repository) - - await process_assistant( - assistant_id, - notification_uuid, - task_id, - tasks_service, - user_id, - ) - - except Exception as e: - await async_session.rollback() - raise e - finally: - await async_session.close() - - -@celery.task( - retries=3, - default_retry_delay=1, - name="process_file_task", - autoretry_for=(Exception,), - dont_autoretry_for=(FileExistsError,), -) -def process_file_task( - file_name: str, - file_original_name: str, - brain_id: UUID, - notification_id: UUID, - knowledge_id: UUID, - source: str | None = None, - source_link: str | None = None, - delete_file: bool = False, -): - if async_engine is None: - init_worker() - - loop = asyncio.get_event_loop() - loop.run_until_complete( - aprocess_file_task( - file_name=file_name, - file_original_name=file_original_name, - brain_id=brain_id, - notification_id=notification_id, - knowledge_id=knowledge_id, - source=source, - source_link=source_link, - delete_file=delete_file, - ) - ) - - -async def aprocess_file_task( - file_name: str, - file_original_name: str, - brain_id: UUID, - notification_id: UUID, - knowledge_id: UUID, - source: str | None = None, - source_link: str | None = None, - delete_file: bool = False, -): - global engine - assert engine - async with AsyncSession(async_engine) as async_session: - try: - await async_session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - with Session(engine, expire_on_commit=False, autoflush=False) as session: - session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - vector_repository = VectorRepository(session) - vector_service = VectorService( - vector_repository - ) # FIXME @amine: fix to need AsyncSession in vector Service - knowledge_repository = KnowledgeRepository(async_session) - knowledge_service = KnowledgeService(knowledge_repository) - await process_uploaded_file( - supabase_client=supabase_client, - brain_service=brain_service, - vector_service=vector_service, - knowledge_service=knowledge_service, - file_name=file_name, - brain_id=brain_id, - file_original_name=file_original_name, - knowledge_id=knowledge_id, - integration=source, - integration_link=source_link, - delete_file=delete_file, - ) - session.commit() - await async_session.commit() - except Exception as e: - session.rollback() - await async_session.rollback() - raise e - finally: - session.close() - await async_session.close() - - -@celery.task( - retries=3, - default_retry_delay=1, - name="process_crawl_task", - autoretry_for=(Exception,), -) -def process_crawl_task( - crawl_website_url: str, - brain_id: UUID, - knowledge_id: UUID, - notification_id: UUID | None = None, -): - logger.info( - f"Task process_crawl_task started for url={crawl_website_url}, knowledge_id={knowledge_id}, brain_id={brain_id}, notification_id={notification_id}" - ) - global engine - assert engine - try: - with Session(engine, expire_on_commit=False, autoflush=False) as session: - session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - vector_repository = VectorRepository(session) - vector_service = VectorService(vector_repository) - loop = asyncio.get_event_loop() - loop.run_until_complete( - process_url_func( - url=crawl_website_url, - brain_id=brain_id, - knowledge_id=knowledge_id, - brain_service=brain_service, - vector_service=vector_service, - ) - ) - session.commit() - except Exception as e: - session.rollback() - raise e - finally: - session.close() - - -@celery.task(name="NotionConnectorLoad") -def process_integration_brain_created_initial_load(brain_id, user_id): - notion_connector = NotionConnector(brain_id=brain_id, user_id=user_id) - pages = notion_connector.load() - logger.info("Notion pages: ", len(pages)) - - -@celery.task -def process_integration_brain_sync_user_brain(brain_id, user_id): - notion_connector = NotionConnector(brain_id=brain_id, user_id=user_id) - notion_connector.poll() - - -@celery.task -def ping_telemetry(): - maybe_send_telemetry("ping", {"ping": "pong"}) - - -@celery.task(name="check_is_premium_task") -def check_is_premium_task(): - check_is_premium(supabase_client) - - -@celery.task(name="process_sync_task") -def process_sync_task( - sync_id: int, user_id: str, files_ids: list[str], folder_ids: list[str] -): - global async_engine - assert async_engine - sync = next( - filter(lambda s: s.id == sync_id, sync_active_service.get_syncs_active(user_id)) - ) - loop = asyncio.get_event_loop() - loop.run_until_complete( - process_sync( - sync=sync, - files_ids=files_ids, - folder_ids=folder_ids, - services=SyncServices( - async_engine=async_engine, - sync_active_service=sync_active_service, - sync_user_service=sync_user_service, - sync_files_repo_service=sync_files_repo_service, - storage=storage, - brain_vectors=brain_vectors, - notification_service=notification_service, - ), - ) - ) - - -@celery.task(name="process_active_syncs_task") -def process_active_syncs_task(): - sync_already_running = is_being_executed("process_sync_task") - - if sync_already_running: - logger.info("Sync already running, skipping") - return - global async_engine - assert async_engine - loop = asyncio.get_event_loop() - loop.run_until_complete( - process_all_active_syncs( - SyncServices( - async_engine=async_engine, - sync_active_service=sync_active_service, - sync_user_service=sync_user_service, - sync_files_repo_service=sync_files_repo_service, - storage=storage, - brain_vectors=brain_vectors, - notification_service=notification_service, - ), - ) - ) - - -@celery.task(name="process_notion_sync_task") -def process_notion_sync_task(): - global async_engine - assert async_engine - loop = asyncio.get_event_loop() - loop.run_until_complete(process_notion_sync(async_engine)) - - -@celery.task(name="fetch_and_store_notion_files_task") -def fetch_and_store_notion_files_task( - access_token: str, user_id: UUID, sync_user_id: int -): - if async_engine is None: - init_worker() - assert async_engine - try: - logger.debug("Fetching and storing Notion files") - loop = asyncio.get_event_loop() - loop.run_until_complete( - fetch_and_store_notion_files_async( - async_engine, access_token, user_id, sync_user_id - ) - ) - sync_user_service.update_sync_user_status( - sync_user_id=sync_user_id, status=str(SyncsUserStatus.SYNCED) - ) - except Exception: - logger.error("Error fetching and storing Notion files") - sync_user_service.update_sync_user_status( - sync_user_id=sync_user_id, status=str(SyncsUserStatus.ERROR) - ) - - -@celery.task(name="clean_notion_user_syncs") -def clean_notion_user_syncs(): - logger.debug("Cleaning Notion user syncs") - sync_user_service.clean_notion_user_syncs() - - -celery.conf.beat_schedule = { - "ping_telemetry": { - "task": f"{__name__}.ping_telemetry", - "schedule": crontab(minute="*/30", hour="*"), - }, - "process_active_syncs": { - "task": "process_active_syncs_task", - "schedule": crontab(minute="*/1", hour="*"), - }, - "process_premium_users": { - "task": "check_is_premium_task", - "schedule": crontab(minute="*/1", hour="*"), - }, - "process_notion_sync": { - "task": "process_notion_sync_task", - "schedule": crontab(minute="0", hour="*/6"), - }, - "clean_notion_user_syncs": { - "task": "clean_notion_user_syncs", - "schedule": crontab(minute="0", hour="0"), - }, -} diff --git a/backend/worker/quivr_worker/check_premium.py b/backend/worker/quivr_worker/check_premium.py deleted file mode 100644 index 4c5ceb7e0..000000000 --- a/backend/worker/quivr_worker/check_premium.py +++ /dev/null @@ -1,141 +0,0 @@ -import os -from datetime import datetime, timedelta - -from postgrest.exceptions import APIError -from pytz import timezone -from quivr_api.logger import get_logger - -from supabase import Client - -logger = get_logger("celery_worker") - - -# TODO: Remove all this code and use Stripe Webhooks -def check_is_premium(supabase_client: Client): - if os.getenv("DEACTIVATE_STRIPE") == "true": - logger.info("Stripe deactivated, skipping check for premium users") - return True - - paris_tz = timezone("Europe/Paris") - current_time = datetime.now(paris_tz) - current_time_str = current_time.strftime("%Y-%m-%d %H:%M:%S.%f") - logger.debug(f"Current time: {current_time_str}") - - # Define the memoization period (e.g., 1 hour) - memoization_period = timedelta(hours=1) - memoization_cutoff = current_time - memoization_period - - # Fetch all necessary data in bulk - try: - subscriptions = ( - supabase_client.table("subscriptions") - .select("*") - .filter("current_period_end", "gt", current_time_str) - .execute() - ).data - except APIError as e: - logger.error(f"Error fetching subscribtions : {e}") - return - - customers = (supabase_client.table("customers").select("*").execute()).data - - customer_emails = [customer["email"] for customer in customers] - - # Split customer emails into batches of 50 - email_batches = [ - customer_emails[i : i + 20] for i in range(0, len(customer_emails), 20) - ] - - users = [] - for email_batch in email_batches: - batch_users = ( - supabase_client.table("users") - .select("id, email") - .in_("email", email_batch) - .execute() - ).data - users.extend(batch_users) - - product_features = ( - supabase_client.table("product_to_features").select("*").execute() - ).data - - user_settings = (supabase_client.table("user_settings").select("*").execute()).data - - # Create lookup dictionaries for faster access - user_dict = {user["email"]: user["id"] for user in users} - customer_dict = {customer["id"]: customer for customer in customers} - product_dict = { - product["stripe_product_id"]: product for product in product_features - } - settings_dict = {setting["user_id"]: setting for setting in user_settings} - - # Process subscriptions and update user settings - premium_user_ids = set() - settings_to_upsert = {} - for sub in subscriptions: - logger.info(f"Subscription {sub['id']}") - if sub["attrs"]["status"] != "active" and sub["attrs"]["status"] != "trialing": - logger.info(f"Subscription {sub['id']} is not active or trialing") - continue - - customer = customer_dict.get(sub["customer"]) - if not customer: - logger.info(f"No customer found for subscription: {sub['customer']}") - continue - - user_id = user_dict.get(customer["email"]) - if not user_id: - logger.info(f"No user found for customer: {customer['email']}") - continue - - current_settings = settings_dict.get(user_id, {}) - last_check = current_settings.get("last_stripe_check") - - # Skip if the user was checked recently - if last_check and datetime.fromisoformat(last_check) > memoization_cutoff: - premium_user_ids.add(user_id) - logger.info(f"User {user_id} was checked recently") - continue - - user_id = str(user_id) # Ensure user_id is a string - premium_user_ids.add(user_id) - - product_id = sub["attrs"]["items"]["data"][0]["plan"]["product"] - product = product_dict.get(product_id) - if not product: - logger.warning(f"No matching product found for subscription: {sub['id']}") - continue - - settings_to_upsert[user_id] = { - "user_id": user_id, - "max_brains": product["max_brains"], - "max_brain_size": product["max_brain_size"], - "monthly_chat_credit": product["monthly_chat_credit"], - "api_access": product["api_access"], - "is_premium": True, - "last_stripe_check": current_time_str, - } - logger.info(f"Upserting settings for user {user_id}") - - # Bulk upsert premium user settings in batches of 10 - settings_list = list(settings_to_upsert.values()) - logger.info(f"Upserting {len(settings_list)} settings") - for i in range(0, len(settings_list), 10): - batch = settings_list[i : i + 10] - supabase_client.table("user_settings").upsert(batch).execute() - - # Delete settings for non-premium users in batches of 10 - settings_to_delete = [ - setting["user_id"] - for setting in user_settings - if setting["user_id"] not in premium_user_ids and setting.get("is_premium") - ] - for i in range(0, len(settings_to_delete), 10): - batch = settings_to_delete[i : i + 10] - supabase_client.table("user_settings").delete().in_("user_id", batch).execute() - - logger.info( - f"Updated {len(settings_to_upsert)} premium users, deleted settings for {len(settings_to_delete)} non-premium users" - ) - return True diff --git a/backend/worker/quivr_worker/files.py b/backend/worker/quivr_worker/files.py deleted file mode 100644 index 8648c7ba9..000000000 --- a/backend/worker/quivr_worker/files.py +++ /dev/null @@ -1,106 +0,0 @@ -import hashlib -import time -from contextlib import contextmanager -from pathlib import Path -from tempfile import NamedTemporaryFile -from typing import Any -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_core.files.file import FileExtension, QuivrFile - -from quivr_worker.utils.utils import get_tmp_name - -logger = get_logger("celery_worker") - - -def compute_sha1(content: bytes) -> str: - m = hashlib.sha1() - m.update(content) - return m.hexdigest() - - -@contextmanager -def build_file( - file_data: bytes, - knowledge_id: UUID, - file_name: str, - original_file_name: str | None = None, -): - try: - # TODO(@aminediro) : Maybe use fsspec file to be agnostic to where files are stored :? - # We are reading the whole file to memory, which doesn't scale - tmp_name, base_file_name, file_extension = get_tmp_name(file_name) - tmp_file = NamedTemporaryFile( - suffix="_" + tmp_name, # pyright: ignore reportPrivateUsage=none - ) - tmp_file.write(file_data) - tmp_file.flush() - file_sha1 = compute_sha1(file_data) - - file_instance = File( - knowledge_id=knowledge_id, - file_name=base_file_name, - original_file_name=( - original_file_name if original_file_name else base_file_name - ), - tmp_file_path=Path(tmp_file.name), - file_size=len(file_data), - file_extension=file_extension, - file_sha1=file_sha1, - ) - yield file_instance - finally: - # Code to release resource, e.g.: - tmp_file.close() - - -class File: - __slots__ = [ - "id", - "file_name", - "tmp_file_path", - "file_size", - "file_extension", - "file_sha1", - "original_file_name", - ] - - def __init__( - self, - knowledge_id: UUID, - file_name: str, - tmp_file_path: Path, - file_size: int, - file_extension: str, - file_sha1: str, - original_file_name: str, - ): - self.id = knowledge_id - self.file_name = file_name - self.tmp_file_path = tmp_file_path - self.file_size = file_size - self.file_sha1 = file_sha1 - self.file_extension = FileExtension(file_extension) - self.original_file_name = original_file_name - - def is_empty(self): - return self.file_size < 1 # pyright: ignore reportPrivateUsage=none - - def to_qfile(self, brain_id: UUID, metadata: dict[str, Any] = {}) -> QuivrFile: - return QuivrFile( - id=self.id, - original_filename=self.file_name, - path=self.tmp_file_path, - brain_id=brain_id, - file_sha1=self.file_sha1, - file_extension=self.file_extension, - file_size=self.file_size, - metadata={ - "date": time.strftime("%Y%m%d"), - "file_name": self.file_name, - "original_file_name": self.original_file_name, - "knowledge_id": self.id, - **metadata, - }, - ) diff --git a/backend/worker/quivr_worker/parsers/__init__.py b/backend/worker/quivr_worker/parsers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/parsers/audio.py b/backend/worker/quivr_worker/parsers/audio.py deleted file mode 100644 index 533357e28..000000000 --- a/backend/worker/quivr_worker/parsers/audio.py +++ /dev/null @@ -1,44 +0,0 @@ -import time - -from langchain.schema import Document -from langchain.text_splitter import RecursiveCharacterTextSplitter -from openai import OpenAI - -from quivr_worker.files import File, compute_sha1 - - -def process_audio(file: File, model: str = "whisper=1"): - # TODO(@aminediro): These should apear in the class processor - # Should be instanciated once per Processor - chunk_size = 500 - chunk_overlap = 0 - text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( - chunk_size=chunk_size, chunk_overlap=chunk_overlap - ) - client = OpenAI() - - dateshort = time.strftime("%Y%m%d-%H%M%S") - file_meta_name = f"audiotranscript_{dateshort}.txt" - with open(file.tmp_file_path, "rb") as audio_file: - transcript = client.audio.transcriptions.create(model=model, file=audio_file) - transcript_txt = transcript.text.encode("utf-8") - - file_size, file_sha1 = len(transcript_txt), compute_sha1(transcript_txt) - texts = text_splitter.split_text(transcript.text) - - docs_with_metadata = [ - Document( - page_content=text, - metadata={ - "file_sha1": file_sha1, - "file_size": file_size, - "file_name": file_meta_name, - "chunk_size": chunk_size, - "chunk_overlap": chunk_overlap, - "date": dateshort, - }, - ) - for text in texts - ] - - return docs_with_metadata diff --git a/backend/worker/quivr_worker/parsers/crawler.py b/backend/worker/quivr_worker/parsers/crawler.py deleted file mode 100644 index b6bec671c..000000000 --- a/backend/worker/quivr_worker/parsers/crawler.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import re -import unicodedata - -from langchain_community.document_loaders import PlaywrightURLLoader -from pydantic import BaseModel -from quivr_api.logger import get_logger - -logger = get_logger("celery_worker") - - -class URL(BaseModel): - url: str - js: bool = False - depth: int = int(os.getenv("CRAWL_DEPTH", "1")) - max_pages: int = 100 - max_time: int = 60 - - -async def extract_from_url(url: URL) -> str: - # Extract and combine content recursively - loader = PlaywrightURLLoader(urls=[url.url], remove_selectors=["header", "footer"]) - - data = await loader.aload() - # Now turn the data into a string - logger.info(f"Extracted content from {len(data)} pages") - extracted_content = "" - for page in data: - extracted_content += page.page_content - return extracted_content - - -def slugify(text): - text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("utf-8") - text = re.sub(r"[^\w\s-]", "", text).strip().lower() - text = re.sub(r"[-\s]+", "-", text) - return text diff --git a/backend/worker/quivr_worker/process/__init__.py b/backend/worker/quivr_worker/process/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/process/process_file.py b/backend/worker/quivr_worker/process/process_file.py deleted file mode 100644 index a13eb833f..000000000 --- a/backend/worker/quivr_worker/process/process_file.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any -from uuid import UUID - -from langchain_core.documents import Document -from quivr_api.logger import get_logger -from quivr_api.modules.brain.entity.brain_entity import BrainEntity -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.vector.service.vector_service import VectorService -from quivr_core.processor.registry import get_processor_class - -from quivr_worker.files import File -from quivr_worker.parsers.audio import process_audio - -logger = get_logger("celery_worker") - -# TODO: remove global -audio_extensions = { - ".m4a", - ".mp3", - ".webm", - ".mp4", - ".mpga", - ".wav", - ".mpeg", -} - - -async def process_file( - file_instance: File, - brain: BrainEntity, - brain_service: BrainService, - vector_service: VectorService, - integration: str | None, - integration_link: str | None, -): - chunks = await parse_file( - file=file_instance, - brain=brain, - integration=integration, - integration_link=integration_link, - ) - store_chunks( - file=file_instance, - brain_id=brain.brain_id, - chunks=chunks, - brain_service=brain_service, - vector_service=vector_service, - ) - - -def store_chunks( - *, - file: File, - brain_id: UUID, - chunks: list[Document], - brain_service: BrainService, - vector_service: VectorService, -): - # vector_ids = document_vector_store.add_documents(chunks) - vector_ids = vector_service.create_vectors(chunks, file.id) - logger.debug(f"Inserted {len(chunks)} chunks in vectors table for {file}") - - if vector_ids is None or len(vector_ids) == 0: - raise Exception(f"Error inserting chunks for file {file.file_name}") - - brain_service.update_brain_last_update_time(brain_id) - - -async def parse_file( - file: File, - brain: BrainEntity, - integration: str | None = None, - integration_link: str | None = None, - **processor_kwargs: dict[str, Any], -) -> list[Document]: - try: - # TODO(@aminediro): add audio procesors to quivr-core - if file.file_extension in audio_extensions: - logger.debug(f"processing audio file {file}") - audio_docs = process_audio_file(file, brain) - return audio_docs - else: - qfile = file.to_qfile( - brain.brain_id, - { - "integration": integration or "", - "integration_link": integration_link or "", - }, - ) - processor_cls = get_processor_class(file.file_extension) - processor = processor_cls(**processor_kwargs) - docs = await processor.process_file(qfile) - logger.debug(f"Parsed {qfile} to : {docs}") - return docs - except KeyError as e: - raise ValueError(f"Can't parse {file}. No available processor") from e - - -def process_audio_file( - file: File, - brain: BrainEntity, -): - try: - result = process_audio(file=file) - if result is None or result == 0: - logger.info( - f"{file.file_name} has been uploaded to brain. There might have been an error while reading it, please make sure the file is not illformed or just an image", # pyright: ignore reportPrivateUsage=none - ) - return [] - logger.info( - f"{file.file_name} has been uploaded to brain {brain.name} in {result} chunks", # pyright: ignore reportPrivateUsage=none - ) - return result - except Exception as e: - logger.exception(f"Error processing audio file {file}: {e}") - raise e diff --git a/backend/worker/quivr_worker/process/process_s3_file.py b/backend/worker/quivr_worker/process/process_s3_file.py deleted file mode 100644 index 99bc4e736..000000000 --- a/backend/worker/quivr_worker/process/process_s3_file.py +++ /dev/null @@ -1,56 +0,0 @@ -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeUpdate -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.vector.service.vector_service import VectorService - -from quivr_worker.files import build_file -from quivr_worker.process.process_file import process_file -from supabase import Client - -logger = get_logger("celery_worker") - - -async def process_uploaded_file( - supabase_client: Client, - brain_service: BrainService, - vector_service: VectorService, - knowledge_service: KnowledgeService, - file_name: str, - brain_id: UUID, - file_original_name: str, - knowledge_id: UUID, - integration: str | None = None, - integration_link: str | None = None, - delete_file: bool = False, - bucket_name: str = "quivr", -): - brain = brain_service.get_brain_by_id(brain_id) - if brain is None: - logger.exception( - "It seems like you're uploading knowledge to an unknown brain." - ) - raise ValueError("unknown brain") - assert brain - file_data = supabase_client.storage.from_(bucket_name).download(file_name) - # TODO: Have the whole logic on do we process file or not - # Don't process a file that already exists (file_sha1 in the table with STATUS=UPLOADED) - # - # - Check on file_sha1 and status - # If we have some knowledge with error - with build_file(file_data, knowledge_id, file_name) as file_instance: - knowledge = await knowledge_service.get_knowledge(knowledge_id=knowledge_id) - await knowledge_service.update_knowledge( - knowledge, - KnowledgeUpdate(file_sha1=file_instance.file_sha1), # type: ignore - ) - await process_file( - file_instance=file_instance, - brain=brain, - brain_service=brain_service, - vector_service=vector_service, - integration=integration, - integration_link=integration_link, - ) diff --git a/backend/worker/quivr_worker/process/process_url.py b/backend/worker/quivr_worker/process/process_url.py deleted file mode 100644 index a5dabecd3..000000000 --- a/backend/worker/quivr_worker/process/process_url.py +++ /dev/null @@ -1,41 +0,0 @@ -from uuid import UUID - -from quivr_api.logger import get_logger -from quivr_api.modules.brain.service.brain_service import BrainService -from quivr_api.modules.vector.service.vector_service import VectorService - -from quivr_worker.files import build_file -from quivr_worker.parsers.crawler import URL, extract_from_url, slugify -from quivr_worker.process.process_file import process_file - -logger = get_logger("celery_worker") - - -async def process_url_func( - url: str, - brain_id: UUID, - knowledge_id: UUID, - brain_service: BrainService, - vector_service: VectorService, -): - crawl_website = URL(url=url) - extracted_content = await extract_from_url(crawl_website) - extracted_content_bytes = extracted_content.encode("utf-8") - file_name = slugify(crawl_website.url) + ".txt" - - brain = brain_service.get_brain_by_id(brain_id) - if brain is None: - logger.error("It seems like you're uploading knowledge to an unknown brain.") - return 1 - - with build_file(extracted_content_bytes, knowledge_id, file_name) as file_instance: - # TODO(@StanGirard): fix bug - # NOTE (@aminediro): I think this might be related to knowledge delete timeouts ? - await process_file( - file_instance=file_instance, - brain=brain, - brain_service=brain_service, - integration=None, - integration_link=None, - vector_service=vector_service, - ) diff --git a/backend/worker/quivr_worker/syncs/__init__.py b/backend/worker/quivr_worker/syncs/__init__.py deleted file mode 100644 index 0ca8a21db..000000000 --- a/backend/worker/quivr_worker/syncs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .process_active_syncs import process_all_active_syncs - -__all__ = ["process_all_active_syncs"] diff --git a/backend/worker/quivr_worker/syncs/process_active_syncs.py b/backend/worker/quivr_worker/syncs/process_active_syncs.py deleted file mode 100644 index 196e54773..000000000 --- a/backend/worker/quivr_worker/syncs/process_active_syncs.py +++ /dev/null @@ -1,150 +0,0 @@ -from datetime import datetime, timedelta -from uuid import UUID - -from notion_client import Client -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.entity.sync_models import SyncsActive -from quivr_api.modules.sync.repository.sync_repository import NotionRepository -from quivr_api.modules.sync.service.sync_notion import ( - SyncNotionService, - fetch_limit_notion_pages, - update_notion_pages, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.sync.utils.syncutils import SyncUtils -from sqlalchemy.ext.asyncio import AsyncEngine -from sqlmodel import text -from sqlmodel.ext.asyncio.session import AsyncSession - -from quivr_worker.syncs.utils import SyncServices, build_syncs_utils - -celery_inspector = celery.control.inspect() - -logger = get_logger("celery_worker") - - -async def process_sync( - sync: SyncsActive, - files_ids: list[str], - folder_ids: list[str], - services: SyncServices, -): - async with build_syncs_utils(services) as mapping_syncs_utils: - try: - user_sync = services.sync_user_service.get_sync_user_by_id( - sync.syncs_user_id - ) - services.notification_service.remove_notification_by_id( - sync.notification_id - ) - assert user_sync, f"No user sync found for active sync: {sync}" - sync_util = mapping_syncs_utils[user_sync.provider.lower()] - await sync_util.direct_sync( - sync_active=sync, - user_sync=user_sync, - files_ids=files_ids, - folder_ids=folder_ids, - ) - except KeyError as e: - logger.error( - f"Provider not supported: {e}", - ) - except Exception as e: - logger.error(f"Error direct sync for {sync.id}: {e}") - raise e - - -async def process_all_active_syncs(sync_services: SyncServices): - async with build_syncs_utils(sync_services) as mapping_sync_utils: - await _process_all_active_syncs( - sync_active_service=sync_services.sync_active_service, - sync_user_service=sync_services.sync_user_service, - mapping_syncs_utils=mapping_sync_utils, - notification_service=sync_services.notification_service, - ) - - -async def _process_all_active_syncs( - sync_active_service: SyncService, - sync_user_service: SyncUserService, - mapping_syncs_utils: dict[str, SyncUtils], - notification_service: NotificationService, -): - active_syncs = await sync_active_service.get_syncs_active_in_interval() - logger.debug(f"Found active syncs: {active_syncs}") - for sync in active_syncs: - try: - user_sync = sync_user_service.get_sync_user_by_id(sync.syncs_user_id) - # TODO: this should be global - # NOTE: Remove the global notification - notification_service.remove_notification_by_id(sync.notification_id) - assert user_sync, f"No user sync found for active sync: {sync}" - sync_util = mapping_syncs_utils[user_sync.provider.lower()] - await sync_util.sync(sync_active=sync, user_sync=user_sync) - except KeyError as e: - logger.error( - f"Provider not supported: {e}", - ) - except Exception as e: - logger.error(f"Error syncing: {e}") - continue - - -async def process_notion_sync( - async_engine: AsyncEngine, -): - try: - async with AsyncSession( - async_engine, expire_on_commit=False, autoflush=False - ) as session: - await session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - sync_user_service = SyncUserService() - notion_repository = NotionRepository(session) - notion_service = SyncNotionService(notion_repository) - - # TODO: Add state in sync_user to check if the same fetching is running - # Get active tasks for all workers - active_tasks = celery_inspector.active() - is_uploading_task_running = any( - "fetch_and_store_notion_files" in task - for worker_tasks in active_tasks.values() - for task in worker_tasks - ) - if is_uploading_task_running: - return None - - # Get all notion syncs - notion_syncs = sync_user_service.get_all_notion_user_syncs() - for notion_sync in notion_syncs: - user_id = notion_sync["user_id"] - notion_client = Client(auth=notion_sync["credentials"]["access_token"]) - - # TODO: fetch last_sync_time from table - pages_to_update = fetch_limit_notion_pages( - notion_client, - datetime.now() - timedelta(hours=6), - ) - logger.debug("Number of pages to update: %s", len(pages_to_update)) - if not pages_to_update: - logger.info("No pages to update") - continue - - await update_notion_pages( - notion_service, - pages_to_update, - UUID(user_id), - notion_sync["id"], - notion_client, # type: ignore - ) - await session.commit() - except Exception as e: - await session.rollback() - raise e - finally: - await session.close() diff --git a/backend/worker/quivr_worker/syncs/store_notion.py b/backend/worker/quivr_worker/syncs/store_notion.py deleted file mode 100644 index 2e44524de..000000000 --- a/backend/worker/quivr_worker/syncs/store_notion.py +++ /dev/null @@ -1,51 +0,0 @@ -from datetime import datetime -from uuid import UUID - -from notion_client import Client -from quivr_api.logger import get_logger -from quivr_api.modules.sync.repository.sync_repository import NotionRepository -from quivr_api.modules.sync.service.sync_notion import ( - SyncNotionService, - fetch_limit_notion_pages, - store_notion_pages, -) -from sqlalchemy.ext.asyncio import AsyncEngine -from sqlmodel import text -from sqlmodel.ext.asyncio.session import AsyncSession - -logger = get_logger("celery_worker") - - -async def fetch_and_store_notion_files_async( - async_engine: AsyncEngine, access_token: str, user_id: UUID, sync_user_id: int -): - try: - async with AsyncSession( - async_engine, expire_on_commit=False, autoflush=False - ) as session: - await session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - notion_repository = NotionRepository(session) - notion_service = SyncNotionService(notion_repository) - notion_client = Client(auth=access_token) - all_search_result = fetch_limit_notion_pages( - notion_client, - last_sync_time=datetime(1970, 1, 1, 0, 0, 0), # UNIX EPOCH - ) - logger.debug(f"Notion fetched {len(all_search_result)} pages") - pages = await store_notion_pages( - all_search_result, notion_service, user_id, sync_user_id - ) - if pages: - logger.info(f"stored {len(pages)} from notion for {user_id}") - else: - logger.warn("No notion page fetched") - - # Commit all before exiting - await session.commit() - except Exception as e: - await session.rollback() - raise e - finally: - await session.close() diff --git a/backend/worker/quivr_worker/syncs/utils.py b/backend/worker/quivr_worker/syncs/utils.py deleted file mode 100644 index bbc3c75f8..000000000 --- a/backend/worker/quivr_worker/syncs/utils.py +++ /dev/null @@ -1,91 +0,0 @@ -from contextlib import asynccontextmanager -from dataclasses import dataclass -from typing import AsyncGenerator - -from quivr_api.celery_config import celery -from quivr_api.logger import get_logger -from quivr_api.modules.brain.repository.brains_vectors import BrainsVectors -from quivr_api.modules.knowledge.repository.knowledges import KnowledgeRepository -from quivr_api.modules.knowledge.repository.storage import SupabaseS3Storage -from quivr_api.modules.knowledge.service.knowledge_service import KnowledgeService -from quivr_api.modules.notification.service.notification_service import ( - NotificationService, -) -from quivr_api.modules.sync.repository.sync_files import SyncFilesRepository -from quivr_api.modules.sync.repository.sync_repository import NotionRepository -from quivr_api.modules.sync.service.sync_notion import ( - SyncNotionService, -) -from quivr_api.modules.sync.service.sync_service import SyncService, SyncUserService -from quivr_api.modules.sync.utils.sync import ( - AzureDriveSync, - DropboxSync, - GitHubSync, - GoogleDriveSync, - NotionSync, -) -from quivr_api.modules.sync.utils.syncutils import SyncUtils -from sqlalchemy.ext.asyncio import AsyncEngine -from sqlmodel import text -from sqlmodel.ext.asyncio.session import AsyncSession - -celery_inspector = celery.control.inspect() - -logger = get_logger("celery_worker") - - -@dataclass -class SyncServices: - async_engine: AsyncEngine - sync_active_service: SyncService - sync_user_service: SyncUserService - sync_files_repo_service: SyncFilesRepository - notification_service: NotificationService - brain_vectors: BrainsVectors - storage: SupabaseS3Storage - - -@asynccontextmanager -async def build_syncs_utils( - deps: SyncServices, -) -> AsyncGenerator[dict[str, SyncUtils], None]: - try: - async with AsyncSession( - deps.async_engine, expire_on_commit=False, autoflush=False - ) as session: - await session.execute( - text("SET SESSION idle_in_transaction_session_timeout = '5min';") - ) - notion_repository = NotionRepository(session) - notion_service = SyncNotionService(notion_repository) - knowledge_service = KnowledgeService(KnowledgeRepository(session)) - - mapping_sync_utils = {} - for provider_name, sync_cloud in [ - ("google", GoogleDriveSync()), - ("azure", AzureDriveSync()), - ("dropbox", DropboxSync()), - ("github", GitHubSync()), - ( - "notion", - NotionSync(notion_service=notion_service), - ), # Fixed duplicate "github" key - ]: - provider_sync_util = SyncUtils( - sync_user_service=deps.sync_user_service, - sync_active_service=deps.sync_active_service, - sync_files_repo=deps.sync_files_repo_service, - sync_cloud=sync_cloud, - notification_service=deps.notification_service, - brain_vectors=deps.brain_vectors, - knowledge_service=knowledge_service, - ) - mapping_sync_utils[provider_name] = provider_sync_util - - yield mapping_sync_utils - await session.commit() - except Exception as e: - await session.rollback() - raise e - finally: - await session.close() diff --git a/backend/worker/quivr_worker/utils/__init__.py b/backend/worker/quivr_worker/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/utils/pdf_generator/__init__.py b/backend/worker/quivr_worker/utils/pdf_generator/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Bold.ttf b/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Bold.ttf deleted file mode 100644 index 22987c62d..000000000 Binary files a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Bold.ttf and /dev/null differ diff --git a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Oblique.ttf b/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Oblique.ttf deleted file mode 100644 index 7fde90789..000000000 Binary files a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed-Oblique.ttf and /dev/null differ diff --git a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed.ttf b/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed.ttf deleted file mode 100644 index 3259bc21a..000000000 Binary files a/backend/worker/quivr_worker/utils/pdf_generator/font/DejaVuSansCondensed.ttf and /dev/null differ diff --git a/backend/worker/quivr_worker/utils/pdf_generator/logo.png b/backend/worker/quivr_worker/utils/pdf_generator/logo.png deleted file mode 100644 index 5a945b4e9..000000000 Binary files a/backend/worker/quivr_worker/utils/pdf_generator/logo.png and /dev/null differ diff --git a/backend/worker/quivr_worker/utils/pdf_generator/pdf_generator.py b/backend/worker/quivr_worker/utils/pdf_generator/pdf_generator.py deleted file mode 100644 index e0c4a467f..000000000 --- a/backend/worker/quivr_worker/utils/pdf_generator/pdf_generator.py +++ /dev/null @@ -1,99 +0,0 @@ -import os - -from fpdf import FPDF -from pydantic import BaseModel - - -class PDFModel(BaseModel): - title: str - content: str - - -class PDFGenerator(FPDF): - def __init__(self, pdf_model: PDFModel, *args, **kwargs): - super().__init__(*args, **kwargs) - self.pdf_model = pdf_model - self.add_font( - "DejaVu", - "", - os.path.join(os.path.dirname(__file__), "font/DejaVuSansCondensed.ttf"), - ) - self.add_font( - "DejaVu", - "B", - os.path.join( - os.path.dirname(__file__), "font/DejaVuSansCondensed-Bold.ttf" - ), - ) - self.add_font( - "DejaVu", - "I", - os.path.join( - os.path.dirname(__file__), "font/DejaVuSansCondensed-Oblique.ttf" - ), - ) - - def header(self): - # Logo - logo_path = os.path.join(os.path.dirname(__file__), "logo.png") - self.image(logo_path, 10, 10, 20) # Adjust size as needed - - # Move cursor to right of image - self.set_xy(20, 15) - - # Title - self.set_font("DejaVu", "B", 12) - self.multi_cell(0, 10, self.pdf_model.title, align="C") - self.ln(5) # Padding after title - - def footer(self): - self.set_y(-15) - - self.set_font("DejaVu", "I", 8) - self.set_text_color(169, 169, 169) - self.cell(80, 10, "Generated by Quivr", 0, 0, "C") - self.set_font("DejaVu", "U", 8) - self.set_text_color(0, 0, 255) - self.cell(30, 10, "quivr.app", 0, 0, "C", link="https://www.quivr.com") - - def chapter_body(self): - self.set_font("DejaVu", "", 12) - content_lines = self.pdf_model.content.split("\n") - for line in content_lines: - if line.startswith("# "): - self.ln() # Add line break before header - self.set_font("DejaVu", "B", 16) - self.multi_cell(0, 10, line[2:], markdown=False) - elif line.startswith("## "): - self.ln() # Add line break before header - self.set_font("DejaVu", "B", 14) - self.multi_cell(0, 10, line[3:], markdown=False) - elif line.startswith("### "): - self.ln() # Add line break before header - self.set_font("DejaVu", "B", 12) - self.multi_cell(0, 10, line[4:], markdown=False) - else: - self.set_font("DejaVu", "", 12) - self.multi_cell(0, 10, line, markdown=True) - self.ln() - - def print_pdf(self): - self.add_page() - self.chapter_body() - - -if __name__ == "__main__": - pdf_model = PDFModel( - title="Summary of Legal Services Rendered by Orrick", - content=""" -# Main Header -## Sub Header -### Sub Sub Header -**Summary:** -This is a **summary** of the legal services rendered. This is a summary of the legal services rendered. This is a summary of the legal services rendered. This is a summary of the legal services rendered. This is a summary of the legal services rendered. -Hello world -""", - ) - pdf = PDFGenerator(pdf_model) - pdf.print_pdf() - pdf.output("simple.pdf") diff --git a/backend/worker/quivr_worker/utils/utils.py b/backend/worker/quivr_worker/utils/utils.py deleted file mode 100644 index 75978b27c..000000000 --- a/backend/worker/quivr_worker/utils/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -import uuid -from json import JSONEncoder -from pathlib import PosixPath -from typing import Tuple - - -def get_tmp_name(file_name: str) -> Tuple[str, str, str]: - # Filepath is S3 based - tmp_name = file_name.replace("/", "_") - base_file_name = os.path.basename(file_name) - _, file_extension = os.path.splitext(base_file_name) - return tmp_name, base_file_name, file_extension - - -# TODO: This is a hack for making uuid work with supabase clients -# THIS is dangerous, I am patching json globally -def _patch_json(): - _default_encoder = JSONEncoder().default - - def _new_default(self, obj): - if isinstance(obj, uuid.UUID): - return str(obj) - if isinstance(obj, PosixPath): - return str(obj) - return _default_encoder(obj) - - JSONEncoder.default = _new_default # type: ignore diff --git a/backend/worker/tests/__init__.py b/backend/worker/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/tests/conftest.py b/backend/worker/tests/conftest.py deleted file mode 100644 index 7d9828a36..000000000 --- a/backend/worker/tests/conftest.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -from uuid import uuid4 - -import pytest -from quivr_worker.files import File - - -@pytest.fixture -def file_instance(tmp_path) -> File: - data = "This is some test data." - temp_file = tmp_path / "data.txt" - temp_file.write_text(data) - knowledge_id = uuid4() - return File( - knowledge_id=knowledge_id, - file_sha1="124", - file_extension=".txt", - file_name=temp_file.name, - original_file_name=temp_file.name, - file_size=len(data), - tmp_file_path=temp_file.absolute(), - ) - - -@pytest.fixture -def audio_file(tmp_path) -> File: - data = os.urandom(128) - temp_file = tmp_path / "data.mp4" - temp_file.write_bytes(data) - knowledge_id = uuid4() - return File( - knowledge_id=knowledge_id, - file_sha1="124", - file_extension=".mp4", - file_name=temp_file.name, - original_file_name="data.mp4", - file_size=len(data), - tmp_file_path=temp_file.absolute(), - ) diff --git a/backend/worker/tests/sample.pdf b/backend/worker/tests/sample.pdf deleted file mode 100644 index 69649317e..000000000 Binary files a/backend/worker/tests/sample.pdf and /dev/null differ diff --git a/backend/worker/tests/test_process_file.py b/backend/worker/tests/test_process_file.py deleted file mode 100644 index 9ca8a49b7..000000000 --- a/backend/worker/tests/test_process_file.py +++ /dev/null @@ -1,105 +0,0 @@ -import datetime -import os -from pathlib import Path -from uuid import uuid4 - -import pytest -from quivr_api.modules.brain.entity.brain_entity import BrainEntity, BrainType -from quivr_core.files.file import FileExtension -from quivr_worker.files import File, build_file -from quivr_worker.parsers.crawler import URL, slugify -from quivr_worker.process.process_file import parse_file - - -def test_build_file(): - random_bytes = os.urandom(128) - brain_id = uuid4() - file_name = f"{brain_id}/test_file.txt" - knowledge_id = uuid4() - - with build_file(random_bytes, knowledge_id, file_name) as file: - assert file.file_size == 128 - assert file.file_name == "test_file.txt" - assert file.id == knowledge_id - assert file.file_extension == FileExtension.txt - - -def test_build_url(): - random_bytes = os.urandom(128) - crawl_website = URL(url="http://url.url") - file_name = slugify(crawl_website.url) + ".txt" - knowledge_id = uuid4() - - with build_file( - random_bytes, - knowledge_id, - file_name=file_name, - original_file_name=crawl_website.url, - ) as file: - qfile = file.to_qfile(brain_id=uuid4()) - assert qfile.metadata["original_file_name"] == crawl_website.url - assert qfile.metadata["file_name"] == file_name - - -@pytest.mark.asyncio -async def test_parse_audio(monkeypatch, audio_file): - from openai.resources.audio.transcriptions import Transcriptions - from openai.types.audio.transcription import Transcription - - def transcribe(*args, **kwargs): - return Transcription(text="audio data") - - monkeypatch.setattr(Transcriptions, "create", transcribe) - brain = BrainEntity( - brain_id=uuid4(), - name="test", - brain_type=BrainType.doc, - last_update=datetime.datetime.now(), - ) - chunks = await parse_file( - file=audio_file, - brain=brain, - ) - assert len(chunks) > 0 - assert chunks[0].page_content == "audio data" - - -@pytest.mark.asyncio -async def test_parse_file(file_instance): - brain = BrainEntity( - brain_id=uuid4(), - name="test", - brain_type=BrainType.doc, - last_update=datetime.datetime.now(), - ) - chunks = await parse_file( - file=file_instance, - brain=brain, - ) - assert len(chunks) > 0 - - -@pytest.mark.asyncio -async def test_parse_file_pdf(): - file_instance = File( - knowledge_id=uuid4(), - file_sha1="124", - file_extension=".pdf", - file_name="test", - original_file_name="test", - file_size=1000, - tmp_file_path=Path("./tests/sample.pdf"), - ) - brain = BrainEntity( - brain_id=uuid4(), - name="test", - brain_type=BrainType.doc, - last_update=datetime.datetime.now(), - ) - chunks = await parse_file( - file=file_instance, - brain=brain, - ) - - assert len(chunks[0].page_content) > 0 - assert len(chunks) > 0 diff --git a/backend/worker/tests/test_process_file_task.py b/backend/worker/tests/test_process_file_task.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/worker/tests/test_process_url_task.py b/backend/worker/tests/test_process_url_task.py deleted file mode 100644 index a34501b52..000000000 --- a/backend/worker/tests/test_process_url_task.py +++ /dev/null @@ -1,125 +0,0 @@ -import asyncio -import os -from typing import List, Tuple -from uuid import uuid4 - -import pytest -import pytest_asyncio -import sqlalchemy -from quivr_api.celery_config import celery -from quivr_api.modules.brain.entity.brain_entity import Brain, BrainType -from quivr_api.modules.knowledge.entity.knowledge import KnowledgeDB -from quivr_api.modules.user.entity.user_identity import User -from quivr_worker.parsers.crawler import URL, extract_from_url -from sqlalchemy.ext.asyncio import create_async_engine -from sqlmodel import select -from sqlmodel.ext.asyncio.session import AsyncSession - -pg_database_base_url = "postgres:postgres@localhost:54322/postgres" - -async_engine = create_async_engine( - "postgresql+asyncpg://" + pg_database_base_url, - echo=True if os.getenv("ORM_DEBUG") else False, - future=True, - pool_pre_ping=True, - pool_size=10, - pool_recycle=0.1, -) - - -TestData = Tuple[Brain, List[KnowledgeDB]] - - -@pytest_asyncio.fixture(scope="function") -async def session(): - print("\nSESSION_EVEN_LOOP", id(asyncio.get_event_loop())) - async with async_engine.connect() as conn: - trans = await conn.begin() - nested = await conn.begin_nested() - async_session = AsyncSession( - conn, - expire_on_commit=False, - autoflush=False, - autocommit=False, - ) - - @sqlalchemy.event.listens_for( - async_session.sync_session, "after_transaction_end" - ) - def end_savepoint(session, transaction): - nonlocal nested - if not nested.is_active: - nested = conn.sync_connection.begin_nested() - - yield async_session - await trans.rollback() - await async_session.close() - - -@pytest_asyncio.fixture() -async def test_data(session: AsyncSession) -> TestData: - user_1 = ( - await session.exec(select(User).where(User.email == "admin@quivr.app")) - ).one() - assert user_1.id - # Brain data - brain_1 = Brain( - name="test_brain", - description="this is a test brain", - brain_type=BrainType.integration, - ) - - knowledge_brain_1 = KnowledgeDB( - file_name="test_file_1", - extension="txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha1", - brains=[brain_1], - user_id=user_1.id, - ) - - knowledge_brain_2 = KnowledgeDB( - file_name="test_file_2", - extension="txt", - status="UPLOADED", - source="test_source", - source_link="test_source_link", - file_size=100, - file_sha1="test_sha2", - brains=[], - user_id=user_1.id, - ) - - session.add(brain_1) - session.add(knowledge_brain_1) - session.add(knowledge_brain_2) - await session.commit() - return brain_1, [knowledge_brain_1, knowledge_brain_2] - - -@pytest.mark.skip -def test_crawl(): - url = "https://en.wikipedia.org/wiki/Python_(programming_language)" - crawl_website = URL(url=url) - extracted_content = extract_from_url(crawl_website) - - assert len(extracted_content) > 1 - - -@pytest.mark.skip -def test_process_crawl_task(test_data: TestData): - brain, [knowledge, _] = test_data - url = "https://en.wikipedia.org/wiki/Python_(programming_language)" - task = celery.send_task( - "process_crawl_task", - kwargs={ - "crawl_website_url": url, - "brain_id": brain.brain_id, - "knowledge_id": knowledge.id, - "notification_id": uuid4(), - }, - ) - result = task.wait() # noqa: F841 diff --git a/backend/worker/tests/test_utils.py b/backend/worker/tests/test_utils.py deleted file mode 100644 index 492da7a1e..000000000 --- a/backend/worker/tests/test_utils.py +++ /dev/null @@ -1,38 +0,0 @@ -import json -from pathlib import PosixPath -from uuid import UUID - -import pytest -from langchain_core.documents import Document -from quivr_worker.utils.utils import _patch_json - - -def test_patch_json(): - c = Document( - page_content="content", - metadata={ - "id": UUID("a45d9cb8-c05e-41e6-a300-2a54cfdb3f85"), - "chunk_index": 1, - "quivr_core_version": "0.0.12", - "qfile_id": UUID("774ad13d-cde7-40ba-9969-0f71208fc692"), - "qfile_path": PosixPath( - "/tmp/pytest-of-amine/pytest-187/test_parse_file0/data.txt" - ), - "original_file_name": "data.txt", - "file_sha1": "124", - "file_size": 23, - "date": "20240804", - "knowledge_id": UUID("774ad13d-cde7-40ba-9969-0f71208fc692"), - "integration": "", - "integration_link": "", - "chunk_size": 6, - "processor_cls": "TextLoader", - "splitter": {"chunk_size": 400, "chunk_overlap": 100}, - }, - ) - - with pytest.raises(TypeError): - json.dumps(c.to_json()) - - _patch_json() - assert json.dumps(c.to_json()) diff --git a/cms/quivr/.editorconfig b/cms/quivr/.editorconfig deleted file mode 100644 index 473e45184..000000000 --- a/cms/quivr/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[{package.json,*.yml}] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false diff --git a/cms/quivr/.env.example b/cms/quivr/.env.example deleted file mode 100644 index 4be889b2a..000000000 --- a/cms/quivr/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -HOST=0.0.0.0 -PORT=1337 -APP_KEYS="toBeModified1,toBeModified2" -API_TOKEN_SALT=tobemodified -ADMIN_JWT_SECRET=tobemodified -TRANSFER_TOKEN_SALT=tobemodified -JWT_SECRET=tobemodified - -# Database -DATABASE_CLIENT=postgres -# DATABASE_FILENAME=.tmp/data.db -DATABASE_HOST= -DATABASE_PORT=5432 -DATABASE_NAME=postgres -DATABASE_USERNAME=postgres -DATABASE_PASSWORD= -DATABASE_SSL=false -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_REGION= -AWS_BUCKET= -RESEND_API_KEY= diff --git a/cms/quivr/.eslintignore b/cms/quivr/.eslintignore deleted file mode 100644 index 90759a584..000000000 --- a/cms/quivr/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -.cache -build -**/node_modules/** diff --git a/cms/quivr/.eslintrc b/cms/quivr/.eslintrc deleted file mode 100644 index b2ca93b1a..000000000 --- a/cms/quivr/.eslintrc +++ /dev/null @@ -1,27 +0,0 @@ -{ - "parser": "babel-eslint", - "extends": "eslint:recommended", - "env": { - "commonjs": true, - "es6": true, - "node": true, - "browser": false - }, - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": false - }, - "sourceType": "module" - }, - "globals": { - "strapi": true - }, - "rules": { - "indent": ["error", 2, { "SwitchCase": 1 }], - "linebreak-style": ["error", "unix"], - "no-console": 0, - "quotes": ["error", "single"], - "semi": ["error", "always"] - } -} diff --git a/cms/quivr/.gitignore b/cms/quivr/.gitignore deleted file mode 100644 index 902275379..000000000 --- a/cms/quivr/.gitignore +++ /dev/null @@ -1,114 +0,0 @@ -############################ -# OS X -############################ - -.DS_Store -.AppleDouble -.LSOverride -Icon -.Spotlight-V100 -.Trashes -._* - - -############################ -# Linux -############################ - -*~ - - -############################ -# Windows -############################ - -Thumbs.db -ehthumbs.db -Desktop.ini -$RECYCLE.BIN/ -*.cab -*.msi -*.msm -*.msp - - -############################ -# Packages -############################ - -*.7z -*.csv -*.dat -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip -*.com -*.class -*.dll -*.exe -*.o -*.seed -*.so -*.swo -*.swp -*.swn -*.swm -*.out -*.pid - - -############################ -# Logs and databases -############################ - -.tmp -*.log -*.sql -*.sqlite -*.sqlite3 - - -############################ -# Misc. -############################ - -*# -ssl -.idea -nbproject -public/uploads/* -!public/uploads/.gitkeep - -############################ -# Node.js -############################ - -lib-cov -lcov.info -pids -logs -results -node_modules -.node_history - -############################ -# Tests -############################ - -coverage - -############################ -# Strapi -############################ - -.env -license.txt -exports -*.cache -dist -build -.strapi-updater.json diff --git a/cms/quivr/Dockerfile b/cms/quivr/Dockerfile deleted file mode 100644 index ec2c9edbb..000000000 --- a/cms/quivr/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:18.13.0-alpine -# Install Python and essential build tools -RUN apk add --update --no-cache python3 make g++ git && ln -sf python3 /usr/bin/python -RUN python3 -m ensurepip -RUN pip3 install --no-cache --upgrade pip setuptools - -# Create the directory on the node image -# where our Next.js app will live -RUN mkdir -p /app - -# Set /app as the working directory -WORKDIR /app - -# Copy package.json and yarn.lock -# to the /app working directory -COPY package*.json yarn.lock ./ - -# Install dependencies in /app -RUN yarn install --network-timeout 1000000 - -# Copy the rest of our Next.js folder into /app -COPY . . - -# Build the Next.js application -RUN yarn build - -# Ensure port 3000 is accessible to our system -EXPOSE 1337 - -# Run yarn start, as we would via the command line -CMD ["yarn", "start"] diff --git a/cms/quivr/README.md b/cms/quivr/README.md deleted file mode 100644 index 879d89b96..000000000 --- a/cms/quivr/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# 🚀 Getting started with Strapi - -Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds. - -### `develop` - -Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop) - -``` -npm run develop -# or -yarn develop -``` - -### `start` - -Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start) - -``` -npm run start -# or -yarn start -``` - -### `build` - -Build your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build) - -``` -npm run build -# or -yarn build -``` - -## âš™ï¸ Deployment - -Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case. - -## 📚 Learn more - -- [Resource center](https://strapi.io/resource-center) - Strapi resource center. -- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation. -- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community. -- [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community. -- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements. - -Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome! - -## ✨ Community - -- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team. -- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members. -- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi. - ---- - -🤫 Psst! [Strapi is hiring](https://strapi.io/careers). diff --git a/cms/quivr/config/admin.js b/cms/quivr/config/admin.js deleted file mode 100644 index 92f535b14..000000000 --- a/cms/quivr/config/admin.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = ({ env }) => ({ - auth: { - secret: env('ADMIN_JWT_SECRET'), - }, - apiToken: { - salt: env('API_TOKEN_SALT'), - }, - transfer: { - token: { - salt: env('TRANSFER_TOKEN_SALT'), - }, - }, -}); diff --git a/cms/quivr/config/api.js b/cms/quivr/config/api.js deleted file mode 100644 index 62f8b65c4..000000000 --- a/cms/quivr/config/api.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - rest: { - defaultLimit: 25, - maxLimit: 100, - withCount: true, - }, -}; diff --git a/cms/quivr/config/database.js b/cms/quivr/config/database.js deleted file mode 100644 index 63a17c3f5..000000000 --- a/cms/quivr/config/database.js +++ /dev/null @@ -1,92 +0,0 @@ -const path = require('path'); - -module.exports = ({ env }) => { - const client = env('DATABASE_CLIENT', 'sqlite'); - - const connections = { - mysql: { - connection: { - connectionString: env('DATABASE_URL'), - host: env('DATABASE_HOST', 'localhost'), - port: env.int('DATABASE_PORT', 3306), - database: env('DATABASE_NAME', 'strapi'), - user: env('DATABASE_USERNAME', 'strapi'), - password: env('DATABASE_PASSWORD', 'strapi'), - ssl: env.bool('DATABASE_SSL', false) && { - key: env('DATABASE_SSL_KEY', undefined), - cert: env('DATABASE_SSL_CERT', undefined), - ca: env('DATABASE_SSL_CA', undefined), - capath: env('DATABASE_SSL_CAPATH', undefined), - cipher: env('DATABASE_SSL_CIPHER', undefined), - rejectUnauthorized: env.bool( - 'DATABASE_SSL_REJECT_UNAUTHORIZED', - true - ), - }, - }, - pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, - }, - mysql2: { - connection: { - host: env('DATABASE_HOST', 'localhost'), - port: env.int('DATABASE_PORT', 3306), - database: env('DATABASE_NAME', 'strapi'), - user: env('DATABASE_USERNAME', 'strapi'), - password: env('DATABASE_PASSWORD', 'strapi'), - ssl: env.bool('DATABASE_SSL', false) && { - key: env('DATABASE_SSL_KEY', undefined), - cert: env('DATABASE_SSL_CERT', undefined), - ca: env('DATABASE_SSL_CA', undefined), - capath: env('DATABASE_SSL_CAPATH', undefined), - cipher: env('DATABASE_SSL_CIPHER', undefined), - rejectUnauthorized: env.bool( - 'DATABASE_SSL_REJECT_UNAUTHORIZED', - true - ), - }, - }, - pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, - }, - postgres: { - connection: { - connectionString: env('DATABASE_URL'), - host: env('DATABASE_HOST', 'localhost'), - port: env.int('DATABASE_PORT', 5432), - database: env('DATABASE_NAME', 'strapi'), - user: env('DATABASE_USERNAME', 'strapi'), - password: env('DATABASE_PASSWORD', 'strapi'), - ssl: env.bool('DATABASE_SSL', false) && { - key: env('DATABASE_SSL_KEY', undefined), - cert: env('DATABASE_SSL_CERT', undefined), - ca: env('DATABASE_SSL_CA', undefined), - capath: env('DATABASE_SSL_CAPATH', undefined), - cipher: env('DATABASE_SSL_CIPHER', undefined), - rejectUnauthorized: env.bool( - 'DATABASE_SSL_REJECT_UNAUTHORIZED', - true - ), - }, - schema: env('DATABASE_SCHEMA', 'public'), - }, - pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, - }, - sqlite: { - connection: { - filename: path.join( - __dirname, - '..', - env('DATABASE_FILENAME', '.tmp/data.db') - ), - }, - useNullAsDefault: true, - }, - }; - - return { - connection: { - client, - ...connections[client], - acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000), - }, - }; -}; diff --git a/cms/quivr/config/middlewares.js b/cms/quivr/config/middlewares.js deleted file mode 100644 index f82459b6d..000000000 --- a/cms/quivr/config/middlewares.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = [ - 'strapi::errors', - { - name: 'strapi::security', - config: { - contentSecurityPolicy: { - useDefaults: true, - directives: { - 'connect-src': ["'self'", 'https:'], - 'img-src': [ - "'self'", - 'data:', - 'blob:', - 'dl.airtable.com', - 'quivr-cms.s3.eu-west-3.amazonaws.com', - '*.strapi.io', - ], - 'media-src': [ - "'self'", - 'data:', - 'blob:', - 'dl.airtable.com', - 'quivr-cms.s3.eu-west-3.amazonaws.com', - '*.strapi.io', - ], - upgradeInsecureRequests: null, - }, - }, - }, - }, - 'strapi::cors', - 'strapi::poweredBy', - 'strapi::logger', - 'strapi::query', - 'strapi::body', - 'strapi::session', - 'strapi::favicon', - 'strapi::public', -]; diff --git a/cms/quivr/config/plugins.js b/cms/quivr/config/plugins.js deleted file mode 100644 index f913f592e..000000000 --- a/cms/quivr/config/plugins.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = ({ env }) => ({ - ckeditor: { - enabled: true, - }, - seo: { - enabled: true, - }, - upload: { - config: { - provider: 'aws-s3', - providerOptions: { - accessKeyId: env('AWS_ACCESS_KEY_ID'), - secretAccessKey: env('AWS_ACCESS_SECRET'), - region: env('AWS_REGION'), - params: { - ACL: env('AWS_ACL', 'public-read'), - signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), - Bucket: env('AWS_BUCKET'), - }, - }, - actionOptions: { - upload: {}, - uploadStream: {}, - delete: {}, - }, - }, - }, - email: { - config: { - provider: 'strapi-provider-email-resend', - providerOptions: { - apiKey: env('RESEND_API_KEY'), // Required - }, - settings: { - defaultFrom: 'cms@mail.quivr.app', - defaultReplyTo: 'cms@mail.quivr.app', - }, - } - }, -}); \ No newline at end of file diff --git a/cms/quivr/config/server.js b/cms/quivr/config/server.js deleted file mode 100644 index 039daec9c..000000000 --- a/cms/quivr/config/server.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), - app: { - keys: env.array('APP_KEYS'), - }, - webhooks: { - populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false), - }, -}); diff --git a/cms/quivr/database/migrations/.gitkeep b/cms/quivr/database/migrations/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cms/quivr/favicon.png b/cms/quivr/favicon.png deleted file mode 100644 index df668a881..000000000 Binary files a/cms/quivr/favicon.png and /dev/null differ diff --git a/cms/quivr/jsconfig.json b/cms/quivr/jsconfig.json deleted file mode 100644 index 4ebd9272c..000000000 --- a/cms/quivr/jsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "nodenext", - "target": "ES2021", - "checkJs": true, - "allowJs": true - } -} diff --git a/cms/quivr/package.json b/cms/quivr/package.json deleted file mode 100644 index 11c1cb1f8..000000000 --- a/cms/quivr/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "quivr", - "version": "0.1.0", - "private": true, - "description": "A Strapi application", - "license": "MIT", - "author": { - "name": "A Strapi developer" - }, - "scripts": { - "build": "strapi build", - "develop": "strapi develop", - "start": "strapi start", - "strapi": "strapi" - }, - "dependencies": { - "@ckeditor/strapi-plugin-ckeditor": "0.0.9", - "@strapi/plugin-i18n": "4.16.2", - "@strapi/plugin-seo": "1.9.6", - "@strapi/plugin-users-permissions": "4.16.2", - "@strapi/provider-upload-aws-s3": "4.14.5", - "@strapi/strapi": "4.16.2", - "better-sqlite3": "8.7.0", - "pg": "8.11.3", - "strapi-provider-email-resend": "1.0.4" - }, - "devDependencies": {}, - "engines": { - "node": "18.19.0", - "npm": "10.2.5" - }, - "strapi": { - "uuid": "c2cbd510-4048-4ef6-b14c-0e3d74744031" - } -} diff --git a/cms/quivr/public/robots.txt b/cms/quivr/public/robots.txt deleted file mode 100644 index ff5d3164e..000000000 --- a/cms/quivr/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# To prevent search engines from seeing the site altogether, uncomment the next two lines: -# User-Agent: * -# Disallow: / diff --git a/cms/quivr/public/uploads/.gitkeep b/cms/quivr/public/uploads/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cms/quivr/src/admin/app.example.js b/cms/quivr/src/admin/app.example.js deleted file mode 100644 index 45cad6180..000000000 --- a/cms/quivr/src/admin/app.example.js +++ /dev/null @@ -1,39 +0,0 @@ -const config = { - locales: [ - // 'ar', - // 'fr', - // 'cs', - // 'de', - // 'dk', - // 'es', - // 'he', - // 'id', - // 'it', - // 'ja', - // 'ko', - // 'ms', - // 'nl', - // 'no', - // 'pl', - // 'pt-BR', - // 'pt', - // 'ru', - // 'sk', - // 'sv', - // 'th', - // 'tr', - // 'uk', - // 'vi', - // 'zh-Hans', - // 'zh', - ], -}; - -const bootstrap = (app) => { - console.log(app); -}; - -export default { - config, - bootstrap, -}; diff --git a/cms/quivr/src/admin/webpack.config.example.js b/cms/quivr/src/admin/webpack.config.example.js deleted file mode 100644 index 1ca45c216..000000000 --- a/cms/quivr/src/admin/webpack.config.example.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/* eslint-disable no-unused-vars */ -module.exports = (config, webpack) => { - // Note: we provide webpack above so you should not `require` it - // Perform customizations to webpack config - // Important: return the modified config - return config; -}; diff --git a/cms/quivr/src/api/.gitkeep b/cms/quivr/src/api/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cms/quivr/src/api/blog/content-types/blog/schema.json b/cms/quivr/src/api/blog/content-types/blog/schema.json deleted file mode 100644 index 449f32441..000000000 --- a/cms/quivr/src/api/blog/content-types/blog/schema.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "blogs", - "info": { - "singularName": "blog", - "pluralName": "blogs", - "displayName": "Blog", - "description": "" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": {}, - "attributes": { - "Article": { - "type": "customField", - "options": { - "output": "HTML", - "preset": "standard" - }, - "customField": "plugin::ckeditor.CKEditor" - }, - "seo": { - "type": "component", - "repeatable": false, - "component": "shared.seo" - }, - "slug": { - "type": "string", - "regex": "^[a-z0-9]+(?:-[a-z0-9]+)*$", - "required": true, - "unique": true - } - } -} diff --git a/cms/quivr/src/api/blog/controllers/blog.js b/cms/quivr/src/api/blog/controllers/blog.js deleted file mode 100644 index 21a34f1be..000000000 --- a/cms/quivr/src/api/blog/controllers/blog.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * blog controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::blog.blog'); diff --git a/cms/quivr/src/api/blog/routes/blog.js b/cms/quivr/src/api/blog/routes/blog.js deleted file mode 100644 index 6e7076963..000000000 --- a/cms/quivr/src/api/blog/routes/blog.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * blog router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::blog.blog'); diff --git a/cms/quivr/src/api/blog/services/blog.js b/cms/quivr/src/api/blog/services/blog.js deleted file mode 100644 index 7f6aa548d..000000000 --- a/cms/quivr/src/api/blog/services/blog.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * blog service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::blog.blog'); diff --git a/cms/quivr/src/api/demo-video/content-types/demo-video/schema.json b/cms/quivr/src/api/demo-video/content-types/demo-video/schema.json deleted file mode 100644 index 3c31d595e..000000000 --- a/cms/quivr/src/api/demo-video/content-types/demo-video/schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "singleType", - "collectionName": "demo_videos", - "info": { - "singularName": "demo-video", - "pluralName": "demo-videos", - "displayName": "Demo-Video" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": {}, - "attributes": { - "Video": { - "allowedTypes": [ - "videos" - ], - "type": "media", - "multiple": false, - "required": true - } - } -} diff --git a/cms/quivr/src/api/demo-video/controllers/demo-video.js b/cms/quivr/src/api/demo-video/controllers/demo-video.js deleted file mode 100644 index eaff18fc3..000000000 --- a/cms/quivr/src/api/demo-video/controllers/demo-video.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * demo-video controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::demo-video.demo-video'); diff --git a/cms/quivr/src/api/demo-video/routes/demo-video.js b/cms/quivr/src/api/demo-video/routes/demo-video.js deleted file mode 100644 index d239804aa..000000000 --- a/cms/quivr/src/api/demo-video/routes/demo-video.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * demo-video router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::demo-video.demo-video'); diff --git a/cms/quivr/src/api/demo-video/services/demo-video.js b/cms/quivr/src/api/demo-video/services/demo-video.js deleted file mode 100644 index e76bfd8cc..000000000 --- a/cms/quivr/src/api/demo-video/services/demo-video.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * demo-video service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::demo-video.demo-video'); diff --git a/cms/quivr/src/api/discussion/content-types/discussion/schema.json b/cms/quivr/src/api/discussion/content-types/discussion/schema.json deleted file mode 100644 index 632fa31a0..000000000 --- a/cms/quivr/src/api/discussion/content-types/discussion/schema.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "discussions", - "info": { - "singularName": "discussion", - "pluralName": "discussions", - "displayName": "Discussion", - "description": "" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "Question": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": true - }, - "Answer": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "text", - "required": true - }, - "use_case": { - "type": "relation", - "relation": "manyToOne", - "target": "api::use-case.use-case", - "inversedBy": "discussions" - } - } -} diff --git a/cms/quivr/src/api/discussion/controllers/discussion.js b/cms/quivr/src/api/discussion/controllers/discussion.js deleted file mode 100644 index 589fea945..000000000 --- a/cms/quivr/src/api/discussion/controllers/discussion.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * discussion controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::discussion.discussion'); diff --git a/cms/quivr/src/api/discussion/routes/discussion.js b/cms/quivr/src/api/discussion/routes/discussion.js deleted file mode 100644 index 364d99364..000000000 --- a/cms/quivr/src/api/discussion/routes/discussion.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * discussion router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::discussion.discussion'); diff --git a/cms/quivr/src/api/discussion/services/discussion.js b/cms/quivr/src/api/discussion/services/discussion.js deleted file mode 100644 index 743d5fdf8..000000000 --- a/cms/quivr/src/api/discussion/services/discussion.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * discussion service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::discussion.discussion'); diff --git a/cms/quivr/src/api/notification-banner/content-types/notification-banner/schema.json b/cms/quivr/src/api/notification-banner/content-types/notification-banner/schema.json deleted file mode 100644 index 22de69272..000000000 --- a/cms/quivr/src/api/notification-banner/content-types/notification-banner/schema.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "kind": "singleType", - "collectionName": "notification_banners", - "info": { - "singularName": "notification-banner", - "pluralName": "notification-banners", - "displayName": "Notification banner" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "notification_id": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "uid", - "required": true - }, - "text": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "richtext", - "required": true - }, - "dismissible": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, - "type": "boolean", - "default": true - }, - "isSticky": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, - "type": "boolean", - "default": true - }, - "style": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, - "type": "json" - } - } -} diff --git a/cms/quivr/src/api/notification-banner/controllers/notification-banner.js b/cms/quivr/src/api/notification-banner/controllers/notification-banner.js deleted file mode 100644 index e447ac50e..000000000 --- a/cms/quivr/src/api/notification-banner/controllers/notification-banner.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * notification-banner controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::notification-banner.notification-banner'); diff --git a/cms/quivr/src/api/notification-banner/routes/notification-banner.js b/cms/quivr/src/api/notification-banner/routes/notification-banner.js deleted file mode 100644 index 3738cbd4e..000000000 --- a/cms/quivr/src/api/notification-banner/routes/notification-banner.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * notification-banner router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::notification-banner.notification-banner'); diff --git a/cms/quivr/src/api/notification-banner/services/notification-banner.js b/cms/quivr/src/api/notification-banner/services/notification-banner.js deleted file mode 100644 index 1f1e63aa4..000000000 --- a/cms/quivr/src/api/notification-banner/services/notification-banner.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * notification-banner service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::notification-banner.notification-banner'); diff --git a/cms/quivr/src/api/security-question/content-types/security-question/schema.json b/cms/quivr/src/api/security-question/content-types/security-question/schema.json deleted file mode 100644 index 07ecd3cbd..000000000 --- a/cms/quivr/src/api/security-question/content-types/security-question/schema.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "security_questions", - "info": { - "singularName": "security-question", - "pluralName": "security-questions", - "displayName": "Security-question", - "description": "" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "question": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string" - }, - "answer": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "text" - } - } -} diff --git a/cms/quivr/src/api/security-question/controllers/security-question.js b/cms/quivr/src/api/security-question/controllers/security-question.js deleted file mode 100644 index a982d4e72..000000000 --- a/cms/quivr/src/api/security-question/controllers/security-question.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * security-question controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::security-question.security-question'); diff --git a/cms/quivr/src/api/security-question/routes/security-question.js b/cms/quivr/src/api/security-question/routes/security-question.js deleted file mode 100644 index 21723e0d7..000000000 --- a/cms/quivr/src/api/security-question/routes/security-question.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * security-question router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::security-question.security-question'); diff --git a/cms/quivr/src/api/security-question/services/security-question.js b/cms/quivr/src/api/security-question/services/security-question.js deleted file mode 100644 index dbccb2d8a..000000000 --- a/cms/quivr/src/api/security-question/services/security-question.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * security-question service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::security-question.security-question'); diff --git a/cms/quivr/src/api/testimonial/content-types/testimonial/schema.json b/cms/quivr/src/api/testimonial/content-types/testimonial/schema.json deleted file mode 100644 index b10285f3e..000000000 --- a/cms/quivr/src/api/testimonial/content-types/testimonial/schema.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "testimonials", - "info": { - "singularName": "testimonial", - "pluralName": "testimonials", - "displayName": "Testimonial" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "url": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": true - }, - "jobTitle": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": true - }, - "content": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "text", - "required": true - }, - "name": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": true - }, - "profilePicture": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string" - }, - "socialMedia": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, - "type": "enumeration", - "enum": [ - "x", - "linkedin" - ], - "required": true - } - } -} diff --git a/cms/quivr/src/api/testimonial/controllers/testimonial.js b/cms/quivr/src/api/testimonial/controllers/testimonial.js deleted file mode 100644 index f53d4d2bf..000000000 --- a/cms/quivr/src/api/testimonial/controllers/testimonial.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * testimonial controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::testimonial.testimonial'); diff --git a/cms/quivr/src/api/testimonial/routes/testimonial.js b/cms/quivr/src/api/testimonial/routes/testimonial.js deleted file mode 100644 index e4dff973c..000000000 --- a/cms/quivr/src/api/testimonial/routes/testimonial.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * testimonial router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::testimonial.testimonial'); diff --git a/cms/quivr/src/api/testimonial/services/testimonial.js b/cms/quivr/src/api/testimonial/services/testimonial.js deleted file mode 100644 index 1859f8abf..000000000 --- a/cms/quivr/src/api/testimonial/services/testimonial.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * testimonial service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::testimonial.testimonial'); diff --git a/cms/quivr/src/api/use-case/content-types/use-case/schema.json b/cms/quivr/src/api/use-case/content-types/use-case/schema.json deleted file mode 100644 index 97fc0083c..000000000 --- a/cms/quivr/src/api/use-case/content-types/use-case/schema.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "use_cases", - "info": { - "singularName": "use-case", - "pluralName": "use-cases", - "displayName": "Use Case", - "description": "" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "name": { - "type": "string", - "required": true, - "pluginOptions": { - "i18n": { - "localized": true - } - } - }, - "description": { - "type": "string", - "required": true, - "pluginOptions": { - "i18n": { - "localized": true - } - } - }, - "discussions": { - "type": "relation", - "relation": "oneToMany", - "target": "api::discussion.discussion", - "mappedBy": "use_case" - } - } -} diff --git a/cms/quivr/src/api/use-case/controllers/use-case.js b/cms/quivr/src/api/use-case/controllers/use-case.js deleted file mode 100644 index 716ab7186..000000000 --- a/cms/quivr/src/api/use-case/controllers/use-case.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * use-case controller - */ - -const { createCoreController } = require('@strapi/strapi').factories; - -module.exports = createCoreController('api::use-case.use-case'); diff --git a/cms/quivr/src/api/use-case/routes/use-case.js b/cms/quivr/src/api/use-case/routes/use-case.js deleted file mode 100644 index 30b596351..000000000 --- a/cms/quivr/src/api/use-case/routes/use-case.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * use-case router - */ - -const { createCoreRouter } = require('@strapi/strapi').factories; - -module.exports = createCoreRouter('api::use-case.use-case'); diff --git a/cms/quivr/src/api/use-case/services/use-case.js b/cms/quivr/src/api/use-case/services/use-case.js deleted file mode 100644 index e0a920d76..000000000 --- a/cms/quivr/src/api/use-case/services/use-case.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * use-case service - */ - -const { createCoreService } = require('@strapi/strapi').factories; - -module.exports = createCoreService('api::use-case.use-case'); diff --git a/cms/quivr/src/components/shared/meta-social.json b/cms/quivr/src/components/shared/meta-social.json deleted file mode 100644 index 5e6e1c781..000000000 --- a/cms/quivr/src/components/shared/meta-social.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "collectionName": "components_shared_meta_socials", - "info": { - "displayName": "metaSocial", - "icon": "project-diagram" - }, - "options": {}, - "attributes": { - "socialNetwork": { - "type": "enumeration", - "enum": [ - "Facebook", - "Twitter" - ], - "required": true - }, - "title": { - "type": "string", - "required": true, - "maxLength": 60 - }, - "description": { - "type": "string", - "maxLength": 65, - "required": true - }, - "image": { - "allowedTypes": [ - "images", - "files", - "videos" - ], - "type": "media", - "multiple": false - } - } -} diff --git a/cms/quivr/src/components/shared/seo.json b/cms/quivr/src/components/shared/seo.json deleted file mode 100644 index ad61b0a88..000000000 --- a/cms/quivr/src/components/shared/seo.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "collectionName": "components_shared_seos", - "info": { - "displayName": "seo", - "icon": "search", - "description": "" - }, - "options": {}, - "attributes": { - "metaTitle": { - "required": true, - "type": "string", - "maxLength": 60 - }, - "metaDescription": { - "type": "string", - "required": true, - "maxLength": 160, - "minLength": 50 - }, - "metaImage": { - "type": "media", - "multiple": false, - "required": true, - "allowedTypes": [ - "images" - ] - }, - "metaSocial": { - "type": "component", - "repeatable": true, - "component": "shared.meta-social" - }, - "keywords": { - "type": "text", - "regex": "[^,]+" - }, - "metaRobots": { - "type": "string", - "regex": "[^,]+" - }, - "structuredData": { - "type": "json" - }, - "metaViewport": { - "type": "string" - }, - "canonicalURL": { - "type": "string" - } - } -} diff --git a/cms/quivr/src/extensions/.gitkeep b/cms/quivr/src/extensions/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cms/quivr/src/index.js b/cms/quivr/src/index.js deleted file mode 100644 index ac5feae26..000000000 --- a/cms/quivr/src/index.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -module.exports = { - /** - * An asynchronous register function that runs before - * your application is initialized. - * - * This gives you an opportunity to extend code. - */ - register(/*{ strapi }*/) {}, - - /** - * An asynchronous bootstrap function that runs before - * your application gets started. - * - * This gives you an opportunity to set up your data model, - * run jobs, or perform some special logic. - */ - bootstrap(/*{ strapi }*/) {}, -}; diff --git a/cms/quivr/types/generated/components.d.ts b/cms/quivr/types/generated/components.d.ts deleted file mode 100644 index 11ca10508..000000000 --- a/cms/quivr/types/generated/components.d.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Schema, Attribute } from '@strapi/strapi'; - -export interface SharedMetaSocial extends Schema.Component { - collectionName: 'components_shared_meta_socials'; - info: { - displayName: 'metaSocial'; - icon: 'project-diagram'; - }; - attributes: { - socialNetwork: Attribute.Enumeration<['Facebook', 'Twitter']> & - Attribute.Required; - title: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - maxLength: 60; - }>; - description: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - maxLength: 65; - }>; - image: Attribute.Media; - }; -} - -export interface SharedSeo extends Schema.Component { - collectionName: 'components_shared_seos'; - info: { - displayName: 'seo'; - icon: 'search'; - description: ''; - }; - attributes: { - metaTitle: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - maxLength: 60; - }>; - metaDescription: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 50; - maxLength: 160; - }>; - metaImage: Attribute.Media & Attribute.Required; - metaSocial: Attribute.Component<'shared.meta-social', true>; - keywords: Attribute.Text; - metaRobots: Attribute.String; - structuredData: Attribute.JSON; - metaViewport: Attribute.String; - canonicalURL: Attribute.String; - }; -} - -declare module '@strapi/types' { - export module Shared { - export interface Components { - 'shared.meta-social': SharedMetaSocial; - 'shared.seo': SharedSeo; - } - } -} diff --git a/cms/quivr/types/generated/contentTypes.d.ts b/cms/quivr/types/generated/contentTypes.d.ts deleted file mode 100644 index 1422e3a52..000000000 --- a/cms/quivr/types/generated/contentTypes.d.ts +++ /dev/null @@ -1,1097 +0,0 @@ -import type { Schema, Attribute } from '@strapi/strapi'; - -export interface AdminPermission extends Schema.CollectionType { - collectionName: 'admin_permissions'; - info: { - name: 'Permission'; - description: ''; - singularName: 'permission'; - pluralName: 'permissions'; - displayName: 'Permission'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - action: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - actionParameters: Attribute.JSON & Attribute.DefaultTo<{}>; - subject: Attribute.String & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - properties: Attribute.JSON & Attribute.DefaultTo<{}>; - conditions: Attribute.JSON & Attribute.DefaultTo<[]>; - role: Attribute.Relation<'admin::permission', 'manyToOne', 'admin::role'>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'admin::permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'admin::permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface AdminUser extends Schema.CollectionType { - collectionName: 'admin_users'; - info: { - name: 'User'; - description: ''; - singularName: 'user'; - pluralName: 'users'; - displayName: 'User'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - firstname: Attribute.String & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - lastname: Attribute.String & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - username: Attribute.String; - email: Attribute.Email & - Attribute.Required & - Attribute.Private & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 6; - }>; - password: Attribute.Password & - Attribute.Private & - Attribute.SetMinMaxLength<{ - minLength: 6; - }>; - resetPasswordToken: Attribute.String & Attribute.Private; - registrationToken: Attribute.String & Attribute.Private; - isActive: Attribute.Boolean & - Attribute.Private & - Attribute.DefaultTo; - roles: Attribute.Relation<'admin::user', 'manyToMany', 'admin::role'> & - Attribute.Private; - blocked: Attribute.Boolean & Attribute.Private & Attribute.DefaultTo; - preferedLanguage: Attribute.String; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'admin::user', 'oneToOne', 'admin::user'> & - Attribute.Private; - updatedBy: Attribute.Relation<'admin::user', 'oneToOne', 'admin::user'> & - Attribute.Private; - }; -} - -export interface AdminRole extends Schema.CollectionType { - collectionName: 'admin_roles'; - info: { - name: 'Role'; - description: ''; - singularName: 'role'; - pluralName: 'roles'; - displayName: 'Role'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - code: Attribute.String & - Attribute.Required & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - description: Attribute.String; - users: Attribute.Relation<'admin::role', 'manyToMany', 'admin::user'>; - permissions: Attribute.Relation< - 'admin::role', - 'oneToMany', - 'admin::permission' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'admin::role', 'oneToOne', 'admin::user'> & - Attribute.Private; - updatedBy: Attribute.Relation<'admin::role', 'oneToOne', 'admin::user'> & - Attribute.Private; - }; -} - -export interface AdminApiToken extends Schema.CollectionType { - collectionName: 'strapi_api_tokens'; - info: { - name: 'Api Token'; - singularName: 'api-token'; - pluralName: 'api-tokens'; - displayName: 'Api Token'; - description: ''; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - description: Attribute.String & - Attribute.SetMinMaxLength<{ - minLength: 1; - }> & - Attribute.DefaultTo<''>; - type: Attribute.Enumeration<['read-only', 'full-access', 'custom']> & - Attribute.Required & - Attribute.DefaultTo<'read-only'>; - accessKey: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - lastUsedAt: Attribute.DateTime; - permissions: Attribute.Relation< - 'admin::api-token', - 'oneToMany', - 'admin::api-token-permission' - >; - expiresAt: Attribute.DateTime; - lifespan: Attribute.BigInteger; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'admin::api-token', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'admin::api-token', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface AdminApiTokenPermission extends Schema.CollectionType { - collectionName: 'strapi_api_token_permissions'; - info: { - name: 'API Token Permission'; - description: ''; - singularName: 'api-token-permission'; - pluralName: 'api-token-permissions'; - displayName: 'API Token Permission'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - action: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - token: Attribute.Relation< - 'admin::api-token-permission', - 'manyToOne', - 'admin::api-token' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'admin::api-token-permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'admin::api-token-permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface AdminTransferToken extends Schema.CollectionType { - collectionName: 'strapi_transfer_tokens'; - info: { - name: 'Transfer Token'; - singularName: 'transfer-token'; - pluralName: 'transfer-tokens'; - displayName: 'Transfer Token'; - description: ''; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - description: Attribute.String & - Attribute.SetMinMaxLength<{ - minLength: 1; - }> & - Attribute.DefaultTo<''>; - accessKey: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - lastUsedAt: Attribute.DateTime; - permissions: Attribute.Relation< - 'admin::transfer-token', - 'oneToMany', - 'admin::transfer-token-permission' - >; - expiresAt: Attribute.DateTime; - lifespan: Attribute.BigInteger; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'admin::transfer-token', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'admin::transfer-token', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface AdminTransferTokenPermission extends Schema.CollectionType { - collectionName: 'strapi_transfer_token_permissions'; - info: { - name: 'Transfer Token Permission'; - description: ''; - singularName: 'transfer-token-permission'; - pluralName: 'transfer-token-permissions'; - displayName: 'Transfer Token Permission'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - action: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 1; - }>; - token: Attribute.Relation< - 'admin::transfer-token-permission', - 'manyToOne', - 'admin::transfer-token' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'admin::transfer-token-permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'admin::transfer-token-permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginUploadFile extends Schema.CollectionType { - collectionName: 'files'; - info: { - singularName: 'file'; - pluralName: 'files'; - displayName: 'File'; - description: ''; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & Attribute.Required; - alternativeText: Attribute.String; - caption: Attribute.String; - width: Attribute.Integer; - height: Attribute.Integer; - formats: Attribute.JSON; - hash: Attribute.String & Attribute.Required; - ext: Attribute.String; - mime: Attribute.String & Attribute.Required; - size: Attribute.Decimal & Attribute.Required; - url: Attribute.String & Attribute.Required; - previewUrl: Attribute.String; - provider: Attribute.String & Attribute.Required; - provider_metadata: Attribute.JSON; - related: Attribute.Relation<'plugin::upload.file', 'morphToMany'>; - folder: Attribute.Relation< - 'plugin::upload.file', - 'manyToOne', - 'plugin::upload.folder' - > & - Attribute.Private; - folderPath: Attribute.String & - Attribute.Required & - Attribute.Private & - Attribute.SetMinMax<{ - min: 1; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::upload.file', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::upload.file', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginUploadFolder extends Schema.CollectionType { - collectionName: 'upload_folders'; - info: { - singularName: 'folder'; - pluralName: 'folders'; - displayName: 'Folder'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.SetMinMax<{ - min: 1; - }>; - pathId: Attribute.Integer & Attribute.Required & Attribute.Unique; - parent: Attribute.Relation< - 'plugin::upload.folder', - 'manyToOne', - 'plugin::upload.folder' - >; - children: Attribute.Relation< - 'plugin::upload.folder', - 'oneToMany', - 'plugin::upload.folder' - >; - files: Attribute.Relation< - 'plugin::upload.folder', - 'oneToMany', - 'plugin::upload.file' - >; - path: Attribute.String & - Attribute.Required & - Attribute.SetMinMax<{ - min: 1; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::upload.folder', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::upload.folder', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginI18NLocale extends Schema.CollectionType { - collectionName: 'i18n_locale'; - info: { - singularName: 'locale'; - pluralName: 'locales'; - collectionName: 'locales'; - displayName: 'Locale'; - description: ''; - }; - options: { - draftAndPublish: false; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.SetMinMax<{ - min: 1; - max: 50; - }>; - code: Attribute.String & Attribute.Unique; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::i18n.locale', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::i18n.locale', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginUsersPermissionsPermission - extends Schema.CollectionType { - collectionName: 'up_permissions'; - info: { - name: 'permission'; - description: ''; - singularName: 'permission'; - pluralName: 'permissions'; - displayName: 'Permission'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - action: Attribute.String & Attribute.Required; - role: Attribute.Relation< - 'plugin::users-permissions.permission', - 'manyToOne', - 'plugin::users-permissions.role' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::users-permissions.permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::users-permissions.permission', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginUsersPermissionsRole extends Schema.CollectionType { - collectionName: 'up_roles'; - info: { - name: 'role'; - description: ''; - singularName: 'role'; - pluralName: 'roles'; - displayName: 'Role'; - }; - pluginOptions: { - 'content-manager': { - visible: false; - }; - 'content-type-builder': { - visible: false; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 3; - }>; - description: Attribute.String; - type: Attribute.String & Attribute.Unique; - permissions: Attribute.Relation< - 'plugin::users-permissions.role', - 'oneToMany', - 'plugin::users-permissions.permission' - >; - users: Attribute.Relation< - 'plugin::users-permissions.role', - 'oneToMany', - 'plugin::users-permissions.user' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::users-permissions.role', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::users-permissions.role', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface PluginUsersPermissionsUser extends Schema.CollectionType { - collectionName: 'up_users'; - info: { - name: 'user'; - description: ''; - singularName: 'user'; - pluralName: 'users'; - displayName: 'User'; - }; - options: { - draftAndPublish: false; - timestamps: true; - }; - attributes: { - username: Attribute.String & - Attribute.Required & - Attribute.Unique & - Attribute.SetMinMaxLength<{ - minLength: 3; - }>; - email: Attribute.Email & - Attribute.Required & - Attribute.SetMinMaxLength<{ - minLength: 6; - }>; - provider: Attribute.String; - password: Attribute.Password & - Attribute.Private & - Attribute.SetMinMaxLength<{ - minLength: 6; - }>; - resetPasswordToken: Attribute.String & Attribute.Private; - confirmationToken: Attribute.String & Attribute.Private; - confirmed: Attribute.Boolean & Attribute.DefaultTo; - blocked: Attribute.Boolean & Attribute.DefaultTo; - role: Attribute.Relation< - 'plugin::users-permissions.user', - 'manyToOne', - 'plugin::users-permissions.role' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'plugin::users-permissions.user', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'plugin::users-permissions.user', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface ApiBlogBlog extends Schema.CollectionType { - collectionName: 'blogs'; - info: { - singularName: 'blog'; - pluralName: 'blogs'; - displayName: 'Blog'; - description: ''; - }; - options: { - draftAndPublish: true; - }; - attributes: { - Article: Attribute.RichText & - Attribute.CustomField< - 'plugin::ckeditor.CKEditor', - { - output: 'HTML'; - preset: 'standard'; - } - >; - seo: Attribute.Component<'shared.seo'>; - slug: Attribute.String & Attribute.Required & Attribute.Unique; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'api::blog.blog', 'oneToOne', 'admin::user'> & - Attribute.Private; - updatedBy: Attribute.Relation<'api::blog.blog', 'oneToOne', 'admin::user'> & - Attribute.Private; - }; -} - -export interface ApiDemoVideoDemoVideo extends Schema.SingleType { - collectionName: 'demo_videos'; - info: { - singularName: 'demo-video'; - pluralName: 'demo-videos'; - displayName: 'Demo-Video'; - }; - options: { - draftAndPublish: true; - }; - attributes: { - Video: Attribute.Media & Attribute.Required; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::demo-video.demo-video', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::demo-video.demo-video', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - -export interface ApiDiscussionDiscussion extends Schema.CollectionType { - collectionName: 'discussions'; - info: { - singularName: 'discussion'; - pluralName: 'discussions'; - displayName: 'Discussion'; - description: ''; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - Question: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - Answer: Attribute.Text & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - use_case: Attribute.Relation< - 'api::discussion.discussion', - 'manyToOne', - 'api::use-case.use-case' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::discussion.discussion', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::discussion.discussion', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - localizations: Attribute.Relation< - 'api::discussion.discussion', - 'oneToMany', - 'api::discussion.discussion' - >; - locale: Attribute.String; - }; -} - -export interface ApiNotificationBannerNotificationBanner - extends Schema.SingleType { - collectionName: 'notification_banners'; - info: { - singularName: 'notification-banner'; - pluralName: 'notification-banners'; - displayName: 'Notification banner'; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - notification_id: Attribute.UID & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - text: Attribute.RichText & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - dismissible: Attribute.Boolean & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }> & - Attribute.DefaultTo; - isSticky: Attribute.Boolean & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }> & - Attribute.DefaultTo; - style: Attribute.JSON & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::notification-banner.notification-banner', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::notification-banner.notification-banner', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - localizations: Attribute.Relation< - 'api::notification-banner.notification-banner', - 'oneToMany', - 'api::notification-banner.notification-banner' - >; - locale: Attribute.String; - }; -} - -export interface ApiSecurityQuestionSecurityQuestion - extends Schema.CollectionType { - collectionName: 'security_questions'; - info: { - singularName: 'security-question'; - pluralName: 'security-questions'; - displayName: 'Security-question'; - description: ''; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - question: Attribute.String & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - answer: Attribute.Text & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::security-question.security-question', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::security-question.security-question', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - localizations: Attribute.Relation< - 'api::security-question.security-question', - 'oneToMany', - 'api::security-question.security-question' - >; - locale: Attribute.String; - }; -} - -export interface ApiTestimonialTestimonial extends Schema.CollectionType { - collectionName: 'testimonials'; - info: { - singularName: 'testimonial'; - pluralName: 'testimonials'; - displayName: 'Testimonial'; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - url: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - jobTitle: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - content: Attribute.Text & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - name: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - profilePicture: Attribute.String & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - socialMedia: Attribute.Enumeration<['x', 'linkedin']> & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::testimonial.testimonial', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::testimonial.testimonial', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - localizations: Attribute.Relation< - 'api::testimonial.testimonial', - 'oneToMany', - 'api::testimonial.testimonial' - >; - locale: Attribute.String; - }; -} - -export interface ApiUseCaseUseCase extends Schema.CollectionType { - collectionName: 'use_cases'; - info: { - singularName: 'use-case'; - pluralName: 'use-cases'; - displayName: 'Use Case'; - description: ''; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - name: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - description: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - discussions: Attribute.Relation< - 'api::use-case.use-case', - 'oneToMany', - 'api::discussion.discussion' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::use-case.use-case', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::use-case.use-case', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - localizations: Attribute.Relation< - 'api::use-case.use-case', - 'oneToMany', - 'api::use-case.use-case' - >; - locale: Attribute.String; - }; -} - -declare module '@strapi/types' { - export module Shared { - export interface ContentTypes { - 'admin::permission': AdminPermission; - 'admin::user': AdminUser; - 'admin::role': AdminRole; - 'admin::api-token': AdminApiToken; - 'admin::api-token-permission': AdminApiTokenPermission; - 'admin::transfer-token': AdminTransferToken; - 'admin::transfer-token-permission': AdminTransferTokenPermission; - 'plugin::upload.file': PluginUploadFile; - 'plugin::upload.folder': PluginUploadFolder; - 'plugin::i18n.locale': PluginI18NLocale; - 'plugin::users-permissions.permission': PluginUsersPermissionsPermission; - 'plugin::users-permissions.role': PluginUsersPermissionsRole; - 'plugin::users-permissions.user': PluginUsersPermissionsUser; - 'api::blog.blog': ApiBlogBlog; - 'api::demo-video.demo-video': ApiDemoVideoDemoVideo; - 'api::discussion.discussion': ApiDiscussionDiscussion; - 'api::notification-banner.notification-banner': ApiNotificationBannerNotificationBanner; - 'api::security-question.security-question': ApiSecurityQuestionSecurityQuestion; - 'api::testimonial.testimonial': ApiTestimonialTestimonial; - 'api::use-case.use-case': ApiUseCaseUseCase; - } - } -} diff --git a/cms/quivr/yarn.lock b/cms/quivr/yarn.lock deleted file mode 100644 index a1193a645..000000000 --- a/cms/quivr/yarn.lock +++ /dev/null @@ -1,10829 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/compat-data@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" - integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== - -"@babel/core@^7.22.20": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" - integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.23.0" - "@babel/helpers" "^7.23.2" - "@babel/parser" "^7.23.0" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" - integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== - dependencies: - "@babel/types" "^7.22.15" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" - integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-plugin-utils@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== - -"@babel/helpers@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" - integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" - -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/parser@^7.22.15", "@babel/parser@^7.22.16": - version "7.22.16" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" - integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== - -"@babel/plugin-syntax-jsx@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" - integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx-self@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" - integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx-source@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c" - integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/runtime-corejs3@^7.9.2": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.22.15.tgz#7aeb9460598a997b0fe74982a5b02fb9e5d264d9" - integrity sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ== - dependencies: - core-js-pure "^3.30.2" - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" - integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.22.15", "@babel/template@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.4.5": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9" - integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.22.15" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.16" - "@babel/types" "^7.22.19" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5": - version "7.22.19" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" - integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.19" - to-fast-properties "^2.0.0" - -"@casl/ability@6.5.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@casl/ability/-/ability-6.5.0.tgz#a151a7637886099b8ffe52a96601225004a5c157" - integrity sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw== - dependencies: - "@ucast/mongo2js" "^1.3.0" - -"@ckeditor/ckeditor5-alignment@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-39.0.2.tgz#a2e08d6ed7be493f89bda9a6415dc56e88a6b8d4" - integrity sha512-TjM+9h3tFn1wgnF4RJg0WaA758kT5VGSaVi0Ti6NECckPGXnccJpIQ7ZMSKAe4zCfaRSmNQsNYc20iq7w7+leg== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-autoformat@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-39.0.2.tgz#b1b8d1b5ba60c96c65848ec58fe8156cc2e526d2" - integrity sha512-iltoRC/XAgApwM7+TErrGMjkX+G13YbB/YxCTXBBQeHiPRnEEpPMeqHGyLMipSshq6mCZaPNwzmfaQ1pQ+PWlg== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-autosave@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-39.0.2.tgz#a598947f031410729dca654e5aebcfdb0d661404" - integrity sha512-2YZwsPSFVwhqVoxU6HtQppmn8+TPnm7LmSKYt+7ESc72oqmWIcFGTkCmP5/gIt4oqz5twtJZ/v5lBg+92i3PyQ== - dependencies: - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-basic-styles@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-39.0.2.tgz#51c8fbad47fd8e3a1a4ece3a4395f0f65f777c70" - integrity sha512-sp5sfE4d8Tng/zIssBK+SgaEkPfGcbmoGkjbHhPlWYAoQB9ONwvWkRZy9s3C+H0gfsEj3EqqxUKY70Us0yT8rA== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-block-quote@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-39.0.2.tgz#0038d5ebe1965280f6ebd9ca8dd8f0d979f7d787" - integrity sha512-ykxkLWJ1nuu5vVBnvHKKgKleudUNTjt/2zDsvUAiBbl4iN0rnOqhLbSL54fY4130p/DvE1CVoozqNiv7L83Vjw== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-clipboard@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-39.0.2.tgz#21a9defa3144e30a86fae67792ea2e958f6a4cdc" - integrity sha512-SSuanBiQ4lWznXSGyeV5vqQweVWZ1+iZG1/atmrZSb/BOWa3XD6EGPu9tuzdm9MCjk2OxBuyMT07Cw+WzXFosQ== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - "@ckeditor/ckeditor5-widget" "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-clipboard@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-40.0.0.tgz#6a2b7e22dbfd001f48930ff33cb297b5c43bb875" - integrity sha512-Xtgjb4ZYa40XHqwQo25X6rA4Job0kgFvocNRMBH7CNrN5h4lJwJwVXlY9HSXvXPY0TBaBBS1HcMvB+sf5DYXeg== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - "@ckeditor/ckeditor5-widget" "40.0.0" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-code-block@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-39.0.2.tgz#02783fe07baffcd4235347ecf4b7191c539d46dc" - integrity sha512-VlG/+VqxrR/JWQZVsGzemr3caOgQLB5OmSsFKPBEzoMI+05zf2UM232DyBVBUfA6UJ5L/ZMkT9YdUi+XhFnhAQ== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-core@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-39.0.2.tgz#fa8a9430f8de53f1edca20d7309f058446842167" - integrity sha512-/xtor5vIXgwBVsAj+yO/wyzezQUmXabdkb/T8aSXtO2665zeOVbDbtSsJ1Ov7Tz5A4Ia1pA9d7iDCt7E8Kva7A== - dependencies: - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-core@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-40.0.0.tgz#941654698c0714263a5aad90bb325504510eb6a5" - integrity sha512-8xoSDOc9/35jEikKtYbdYmBxPop7i/JYSkkZmJYbZ8XxkjQiIMAUYOJVdNntfuLGazU+THmutieEA/x3ISme4g== - dependencies: - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-easy-image@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-39.0.2.tgz#e6a49cf6a636eaaf2d4591c0d9b4d600d53e27d6" - integrity sha512-tOpmHH97Mv0bz844fciqxXe3owUyqqh4MqCMZ7c5cjywuvZOQ8Kp/o7HgWEKKRuKJUoLGaBH0bDRKMpIvXkOVg== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-editor-classic@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-39.0.2.tgz#7ecca6b9308726fb1bc5dc695830ac9c94ac1365" - integrity sha512-zDDFe70junUJT5VltMsVdBqSZUew4fCe8fmi9JMI2m2MAwNwARAOkOy9mQMMTJlrD+P3CfD/Y9QpromEjLOnYA== - dependencies: - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-engine@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-39.0.2.tgz#5cb5ee83539d81645ae40a7462e651fc51c0bda0" - integrity sha512-ERcEpIrmTML0/uhukkC+ZJSOx4mRaPbNG5vPEBXIentfDpzu1NrmUhGZRGXaw5lltL+NJbuTI0wjEINap0Hl3w== - dependencies: - "@ckeditor/ckeditor5-utils" "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-engine@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-40.0.0.tgz#321d19d5206dc4b4d929706c23c436e4ef6c31c7" - integrity sha512-zauOXFudE1B94RSziWWojdpqGprSo4rKwW3KLU6nfaz9Kq9RZkcP/TW5ADE0DxC2jWUMeItVE/3U8ES5fZ0hqQ== - dependencies: - "@ckeditor/ckeditor5-utils" "40.0.0" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-enter@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-39.0.2.tgz#c6bb17f28869415c83f7ea09e9703f2baf15e320" - integrity sha512-7yeZI/mzJH4/yryXFQemrueZA/UNGkfnauN8Mu6XFMykoRNo6l+cASz0x4iGb3pWumV3g+tUeX6AJmuwXkRb5A== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - -"@ckeditor/ckeditor5-enter@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-40.0.0.tgz#48a8d33e34622af9fdc06082d7b0d153c5e75b89" - integrity sha512-pu8/zyQMqzMOgJbbPLbVu6znFfbgMYQwIVT7GMmWX+pZxPSBPyM+qNlmstFLwAxeki0aHCbo27gYmR1rIYGgrg== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - -"@ckeditor/ckeditor5-essentials@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-39.0.2.tgz#9bbb0f1f62d3f60e634dcc5bec366873b756e21c" - integrity sha512-5Nf3XeuB0thXTJS2M2JYrlW2uuSujNOhwW7L05e7Z5JP4nUSkopBHdvymX9feZKMzGPOw6SVhk8gQIDYV7S28A== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-find-and-replace@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-39.0.2.tgz#bb0c6b444c01613b0e1535e59dfec7befe8c27d1" - integrity sha512-cRD/ozsclBVjcuCokFiPMGEUV8YqbyjMQnHMUbsuc3qGQ2rV7A6tJZJqOdW7EW78asEPrVQ6M0SrIYgrb5lzCQ== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-font@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-font/-/ckeditor5-font-39.0.2.tgz#a228151a6ec2b2c65a5e2087a5eec9ab6decf86e" - integrity sha512-6sGgo9X23gAllVjzGH1gOkjIEHkvm2O40IqUKCBwzn0K6Ry6St4R4QtcQeFV5jxAMKn0eD7blPuhItVrjmN0hw== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-heading@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-39.0.2.tgz#2704c3b8c2ffe8f77c30130523275f6f0bd5ad8b" - integrity sha512-P1ogDMAsTZB/TfuNwifNv1H9KHaMSPRaPfd+clhhLV/GAdt2rGMeH0HKuSke54cZYB6qMQP7Za8Xsfi2ufY0GA== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-highlight@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-39.0.2.tgz#4ef924b2f9baba97c3b1ca5ca2234233a64a5cb1" - integrity sha512-WlinrcgNRgTMPCtg3ZfirEdbVpP+xSQdYGA/kmMTcjEnzfEUZv5ceecAF367158+hQNhY06p7Zpvxot8z6pobA== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-horizontal-line@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-39.0.2.tgz#570c75dc3ecf3f2de3a2ef9853703423c58f1993" - integrity sha512-lKPqiOwmuX/9s5TclET7cywcQ76TJh/w+6u3BK8buzlmyWzdeYXUyljNqWCgiS0i2yyY4w5XaJxXB/Ey7Ks8/w== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-html-embed@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-39.0.2.tgz#4876baabd70a5f69248ad627e010053f5486fc46" - integrity sha512-51dt9ndDl8Ba1ETmBDwAM0u82/yyF1uY5bXnHrkfrcYwGn+pHojKMTysilmniY35fo9Rn2TaV8eBsp/2h/tYMg== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-image@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-image/-/ckeditor5-image-39.0.2.tgz#2dc2599dbc3556626b75884cc52e7020c1bac5fa" - integrity sha512-sp7ce+j/UqRW4kmhMX8DfZKPbNs5HgiEQY8ChQUPOKghstEjBrPpGFYCJ++NMPmWguDTzLxVAVuo2k+VOt+swA== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-indent@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-39.0.2.tgz#d7fbca63cbc07181f4208dec0e15d431d1ca42d6" - integrity sha512-wU/1jBqgkK068fxkZDxnZM5iACunABXBVtpgfXqe2S6cMPVQyhiLzo955Yt7BhqN7Mkor59xESO2+Npj/vwB7w== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-language@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-language/-/ckeditor5-language-39.0.2.tgz#db5b99849dcd5dbc3771e174ea8ab50117756304" - integrity sha512-GG5ZGsTDF6fKtfvvcy0w+/YQ24CL2x1lTWiZ1Ktkqb3o4WLHviynO5pAz1UXWViuKpF9QWPpgs4KTF1WeHhCJw== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-link@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-link/-/ckeditor5-link-39.0.2.tgz#398032162d233d511cd2f59134560a3ea48b4111" - integrity sha512-UKSamaD+yGaLPo/7pLS6bi+h2pRpX7qFSe4isVvmC8PfhN4YAm6ZYpIXpnktzfv2+/Jx6P9HzfjOnJPPT5PRdQ== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-list@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-list/-/ckeditor5-list-39.0.2.tgz#ddf856dc1f081ad16a2766d9c1316023ec7bf293" - integrity sha512-XRcp67PuDK/mP0sL8g+TFLIr/MigVHKATwO+7OAJZlGAGxr73V/eRw3G6ZAe6Lh0lVQPRA54r+h5DHG5P+KRRg== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-markdown-gfm@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-39.0.2.tgz#e3d7a34b939d3f68c18a01f0e1d23f2d03764554" - integrity sha512-x4sQHFiQbr0z0pPElevtBpO052iOCokQRJ00ZOHzZc4gOUdxSHikNLZnRcjOm2QVgOudA0C7JuaYq3LhQU3cNQ== - dependencies: - ckeditor5 "39.0.2" - marked "4.0.12" - turndown "6.0.0" - turndown-plugin-gfm "1.0.2" - -"@ckeditor/ckeditor5-media-embed@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-39.0.2.tgz#60edde495c4b51103f2249b1708beaba6740d15d" - integrity sha512-1QL+nYSTKlnTz9rMWeuW0/kvq8eCR0Yl0dJl+37uPJOrR6fQNxVcGq375D9f+WO45IJjOZ+7dtgxm19WK3R1cQ== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-page-break@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-39.0.2.tgz#cd61d7a199ef2b1a8f0375d426bde6ac40a22468" - integrity sha512-usP9v40T8jiCCnbtuhGDlXaH1m4yHPnC8vSl2c4VER/mGXl/87v0YSnYFPtHYaMYCE38917oCoQ8QrNNK8eqmg== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-paragraph@39.0.2", "@ckeditor/ckeditor5-paragraph@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-39.0.2.tgz#c76d4ea994c162b2552983742de8000184f81a65" - integrity sha512-is0+G3ghz+6EwoNT5ciySiPt05FI0I/lxPksSDl4QG9BcGUwkN94fJalCxgXuMlGulojm/YZ3dCmJPnLMrAnUA== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - -"@ckeditor/ckeditor5-paragraph@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-40.0.0.tgz#50be65f332271259c6e06b46d6f5b247a0a305ba" - integrity sha512-j2Pm/dlu3hE08EKYvbUT9qUMyJeZtuufuZjUZaADCsVmtfFuzXlaHjIkQUvCxUeuMXQLlUpJ69OSAMzzMlJs8g== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - -"@ckeditor/ckeditor5-paste-from-office@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-39.0.2.tgz#6b29bcae5947847631cb4ca8937ea14658ef013b" - integrity sha512-Y9O1C+9BpFCtvYI80AJzm/BkZWMedhreCaZcSCsEUUIo6vrIwehatTvgqWZJLwKZwL+4iy5/Kv9OfUhvO10I8Q== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-react@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-react/-/ckeditor5-react-6.1.0.tgz#601db37d0810e52796276c85cce9907d882463e0" - integrity sha512-uwJ89pOJnjlMgfluGJhxatt6zV5ZavuV7AaRvJpxzAU7SjoC3sNumohDRmI0KGDGoyCVPMJO9fgE3ijIb3gHlQ== - dependencies: - prop-types "^15.7.2" - -"@ckeditor/ckeditor5-remove-format@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-39.0.2.tgz#907ca3b38869b1cef5417397914bf412b7b0cb6b" - integrity sha512-gCoWMMUyvqjtQs+EUr7JpO8LEF2Ip7qXmCuSwLTteet79a8sQyllPY6O3UOUEuFMO2GtT36+jtQ93pQh7onn+g== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-select-all@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-39.0.2.tgz#d6559237512725ab01f40a793c79f57761a36af8" - integrity sha512-/zTPZ14sfsL6E7LHcdOSW5JR7A5tsaSO2KNlQ/jm77XWSeQ7HZ4I7hoLXWksvcseubQ+xExWrzMQIjIOGqBLcQ== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - -"@ckeditor/ckeditor5-select-all@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-40.0.0.tgz#15e1eacc2cc7260b34ef98c778c5e8b0fbeb15fe" - integrity sha512-6FP/8DjKCfy9ha42hoshOj5fs13KFFRSbXcCDNFCUk/ZrvH42lz1BYw2S5uQYA9Xl6o4BUWdN+g4CVPQfpGm+Q== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - -"@ckeditor/ckeditor5-special-characters@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-39.0.2.tgz#613c7c6f3d7718d03eb1a7b957be8f7ec1246f06" - integrity sha512-0BSRKkywpzI+KJGB9ouFAf59UYWasUBSGiDrBbdd6QGWwA1hlpaMIPD4B8nCGmSNgtmyVEbpp7wBDfol0GgggA== - dependencies: - ckeditor5 "39.0.2" - -"@ckeditor/ckeditor5-table@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-table/-/ckeditor5-table-39.0.2.tgz#f748561976a40047e12c023f574690fae75c2944" - integrity sha512-IpI53KPEOj1F1vbMYUHLkOeoQoQV/q3pfiJxXXHZja1QaIEnqZT7YcSvJQbCyiuQw4UCZqU5x84c8GcyIZlBhg== - dependencies: - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-theme-lark@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-39.0.2.tgz#37e67ee3113c5fb385e66a2dad3de1033c786260" - integrity sha512-v6S3++hkoKgqV71BCafKWYmPA6o/X44kUzdahnFs1rYBM5WS87y9Jb0W8Rv8FXXD5QD0H2axtySw9S6HfYspQw== - dependencies: - "@ckeditor/ckeditor5-ui" "39.0.2" - -"@ckeditor/ckeditor5-typing@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-39.0.2.tgz#186c28c4d37ee05bc53a84deb69a04e740ed4149" - integrity sha512-ylkBumukIRe/8jQTanVYNcExvoraXX9TIX3POwiug2F+c/POKjoHgeq8+Wf/JEl7/ydtXZKT5tROqOe9haSteA== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-typing@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-40.0.0.tgz#0f5e5aeb261b9fe9c27acf364b83a6dbcf9df5d8" - integrity sha512-c0uMXkh3kJP1wEVoh/0sLPr8Ouk4EvBuaAqSYEkrvX5wKBWAoGnUotHfrFH8wRBy25m17QbZ44N7dkA+BpuMPQ== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-ui@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-39.0.2.tgz#17e6fe0f66554c6e2080bfaef1f94513855f7579" - integrity sha512-OHYasdPXG1Vy1tR5hcGeffeqg6ujWzCEbRczuU+0sC3ttYkxrnFk6qYe7gjA+BoqD61otk1au+rhCVux8dy0hQ== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - color-convert "2.0.1" - color-parse "1.4.2" - lodash-es "4.17.21" - vanilla-colorful "0.7.2" - -"@ckeditor/ckeditor5-ui@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-40.0.0.tgz#30fb7f4a2deab4097886c11c8990728f9697ed22" - integrity sha512-wnfC7eSqdN6i+nHTN83+PCByWeCPDgdQAXvf3HHBNdsJna6khKC8Oy/1eU8F+vR84unJMrPoaCMIx0qRvK3hCA== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - color-convert "2.0.1" - color-parse "1.4.2" - lodash-es "4.17.21" - vanilla-colorful "0.7.2" - -"@ckeditor/ckeditor5-undo@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-39.0.2.tgz#0eddf55c987a6f8240d38618e134a31cc4fab517" - integrity sha512-DSy7rRtnpVPpzZYOoQJNZ/6Zx4LEk3WVf8VG8wJDgpuxbRrE59DsNwVEqGEvDyVGlgzbuWZrfkkcL4hjcoYHCw== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - -"@ckeditor/ckeditor5-undo@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-40.0.0.tgz#788cd65a56bbd9ccbc41a34f0a5a5e782ed24982" - integrity sha512-kt+9Ux0RdC0OeMLp95kJW1h6d1LobOblkozU2as5VN5c20tvbXviQJuoQeJsiixmJapUmi7vvDgnVsGw9obEpQ== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - -"@ckeditor/ckeditor5-upload@39.0.2", "@ckeditor/ckeditor5-upload@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-39.0.2.tgz#3138e2a9a963d20e3ebc39c68ee520e2b3c94332" - integrity sha512-6LLU9lJDlnE4RT1GBzK1V6gsIv/+4NiWVojblzET9AlyMI4BRTn3/PPxZRWyPPKH96AeuIWoyj32D/a24yQujg== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - -"@ckeditor/ckeditor5-upload@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-40.0.0.tgz#bfed41c38b5bb6bba8c68321c931c6e304bcf79b" - integrity sha512-LutDg8zjhJu1UKInAyvVHIk8HyroETi61KS2PQTyiTQv/DmRvjSK32Xl83KprTxAvqZsiDdXe+Nl1kdAO8S2ag== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - -"@ckeditor/ckeditor5-utils@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-39.0.2.tgz#64c0d88ec6354a38ddb86d51341f2d33eb6a8d46" - integrity sha512-aqiGhPJxEihSLW21lGWcAvjVTTwJYxEbfMk1eLf/BEY3euy6iltRC6EqbXkyJDcKGU7cQtk6JXAIkH+D2FF87g== - dependencies: - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-utils@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-40.0.0.tgz#6e287ab33d5f8cac4928f82f5621ba994c47a15e" - integrity sha512-52UwkeGxrZWhbwWWfixKceWhF1kuDeJNAM57wqfB7GS8CzElOpJ3AELeD/L/ZkUEBGL9asqribEH3CzgTjWKPA== - dependencies: - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-watchdog@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-39.0.2.tgz#a8a15b1615f372ee9878405877154943f93a272a" - integrity sha512-9E7BNp9c+nj/01JWF4lBVcDwJI2lN/Vgerw+ex4l/nTRl7crLzckSy3DWhYlpQPjJzsrkFjGQ+zCG4C/Vevl3Q== - dependencies: - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-watchdog@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-40.0.0.tgz#a9ab427fe9a1248cf4d2a1e98729b74cda79af09" - integrity sha512-bwBimEdiUCe6/1IScWhC2vomcwApkKeb804NuxX7m7QhctKaHALV+tRVG/y8Oln3qCaw3na6C4pQgaXz0qtWcQ== - dependencies: - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-widget@39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-39.0.2.tgz#f7e8f278f64be6efe35bc0c5205b62eec23bc584" - integrity sha512-bGs8iKD3c+r3/JCqhXoh/MPJut0JcdUdxDSgS+xYu1UJ03b/K39/0QbdVjT1I6oNRSDb4IUwBO2Xc3zTMl/qTw== - dependencies: - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-enter" "39.0.2" - "@ckeditor/ckeditor5-typing" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-widget@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-40.0.0.tgz#71330fe467fdbf66f075ee10ac7dda1f72a6a2d8" - integrity sha512-3dZjAQECWMvSMQhleM6iHwG2LOTKQmir4Rf3/Vulc7Aexb/brcQ06JuNQtQsIxRBZPoaEpWFAakK1PSfxxO0Sg== - dependencies: - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-enter" "40.0.0" - "@ckeditor/ckeditor5-typing" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - lodash-es "4.17.21" - -"@ckeditor/ckeditor5-word-count@^39.0.2": - version "39.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-39.0.2.tgz#3c5df9b017077e7af9cf4470162e4812059500b2" - integrity sha512-eKXSEEU+SkIgD8Sezj7Lzt8pX1XLkDgMfMGU2LoIB5BXChlV2mR6Zuz9Au2uzkrWmUxTerjtgIrpQZBtrMSeFQ== - dependencies: - ckeditor5 "39.0.2" - lodash-es "4.17.21" - -"@ckeditor/strapi-plugin-ckeditor@0.0.9": - version "0.0.9" - resolved "https://registry.yarnpkg.com/@ckeditor/strapi-plugin-ckeditor/-/strapi-plugin-ckeditor-0.0.9.tgz#41e91b01e7b5df8c8084dd6976f9a5fec357823f" - integrity sha512-YZTR8yQS06Izpp1djaAu3052V91poreZ3fJc2UDpsXOrdZ2rkLdiIiDWi+me9LRO39Y/wOokgQNQAOS2lMxvNw== - dependencies: - "@ckeditor/ckeditor5-alignment" "^39.0.2" - "@ckeditor/ckeditor5-autoformat" "^39.0.2" - "@ckeditor/ckeditor5-autosave" "^39.0.2" - "@ckeditor/ckeditor5-basic-styles" "^39.0.2" - "@ckeditor/ckeditor5-block-quote" "^39.0.2" - "@ckeditor/ckeditor5-code-block" "^39.0.2" - "@ckeditor/ckeditor5-easy-image" "^39.0.2" - "@ckeditor/ckeditor5-editor-classic" "^39.0.2" - "@ckeditor/ckeditor5-essentials" "^39.0.2" - "@ckeditor/ckeditor5-find-and-replace" "^39.0.2" - "@ckeditor/ckeditor5-font" "^39.0.2" - "@ckeditor/ckeditor5-heading" "^39.0.2" - "@ckeditor/ckeditor5-highlight" "^39.0.2" - "@ckeditor/ckeditor5-horizontal-line" "^39.0.2" - "@ckeditor/ckeditor5-html-embed" "^39.0.2" - "@ckeditor/ckeditor5-image" "^39.0.2" - "@ckeditor/ckeditor5-indent" "^39.0.2" - "@ckeditor/ckeditor5-language" "^39.0.2" - "@ckeditor/ckeditor5-link" "^39.0.2" - "@ckeditor/ckeditor5-list" "^39.0.2" - "@ckeditor/ckeditor5-markdown-gfm" "^39.0.2" - "@ckeditor/ckeditor5-media-embed" "^39.0.2" - "@ckeditor/ckeditor5-page-break" "^39.0.2" - "@ckeditor/ckeditor5-paragraph" "^39.0.2" - "@ckeditor/ckeditor5-paste-from-office" "^39.0.2" - "@ckeditor/ckeditor5-react" "^6.1.0" - "@ckeditor/ckeditor5-remove-format" "^39.0.2" - "@ckeditor/ckeditor5-special-characters" "^39.0.2" - "@ckeditor/ckeditor5-table" "^39.0.2" - "@ckeditor/ckeditor5-theme-lark" "^39.0.2" - "@ckeditor/ckeditor5-upload" "^39.0.2" - "@ckeditor/ckeditor5-word-count" "^39.0.2" - "@reinmar/ckeditor5-maximum-length" "^0.0.1" - ckeditor5 "^39.0.2" - -"@codemirror/autocomplete@^6.0.0": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.9.1.tgz#e0989c6a33a37604b5d2c896dcca7562ae3d7c61" - integrity sha512-yma56tqD7khIZK4gy4X5lX3/k5ArMiCGat7HEWRF/8L2kqOjVdp2qKZqpcJjwTIjSj6fqKAHqi7IjtH3QFE+Bw== - dependencies: - "@codemirror/language" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.17.0" - "@lezer/common" "^1.0.0" - -"@codemirror/commands@^6.0.0", "@codemirror/commands@^6.1.0": - version "6.2.5" - resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.2.5.tgz#e889f93f9cc85b32f6b2844d85d08688f695a6b8" - integrity sha512-dSi7ow2P2YgPBZflR9AJoaTHvqmeGIgkhignYMd5zK5y6DANTvxKxp6eMEpIDUJkRAaOY/TFZ4jP1ADIO/GLVA== - dependencies: - "@codemirror/language" "^6.0.0" - "@codemirror/state" "^6.2.0" - "@codemirror/view" "^6.0.0" - "@lezer/common" "^1.0.0" - -"@codemirror/lang-json@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz#0a0be701a5619c4b0f8991f9b5e95fe33f462330" - integrity sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ== - dependencies: - "@codemirror/language" "^6.0.0" - "@lezer/json" "^1.0.0" - -"@codemirror/language@^6.0.0": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.9.1.tgz#97e2c3e44cf4ff152add865ed7ecec73868446a4" - integrity sha512-lWRP3Y9IUdOms6DXuBpoWwjkR7yRmnS0hKYCbSfPz9v6Em1A1UCRujAkDiCrdYfs1Z0Eu4dGtwovNPStIfkgNA== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - "@lezer/common" "^1.1.0" - "@lezer/highlight" "^1.0.0" - "@lezer/lr" "^1.0.0" - style-mod "^4.0.0" - -"@codemirror/lint@^6.0.0": - version "6.4.2" - resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.4.2.tgz#c13be5320bde9707efdc94e8bcd3c698abae0b92" - integrity sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - crelt "^1.0.5" - -"@codemirror/search@^6.0.0": - version "6.5.4" - resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.4.tgz#54005697bf581f7dccbbb4a0c34d3a7aa25a513a" - integrity sha512-YoTrvjv9e8EbPs58opjZKyJ3ewFrVSUzQ/4WXlULQLSDDr1nGPJ67mMXFNNVYwdFhybzhrzrtqgHmtpJwIF+8g== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - crelt "^1.0.5" - -"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.1.4", "@codemirror/state@^6.2.0": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.1.tgz#6dc8d8e5abb26b875e3164191872d69a5e85bd73" - integrity sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw== - -"@codemirror/theme-one-dark@^6.0.0": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz#fcef9f9cfc17a07836cb7da17c9f6d7231064df8" - integrity sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA== - dependencies: - "@codemirror/language" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - "@lezer/highlight" "^1.0.0" - -"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0": - version "6.20.0" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.20.0.tgz#6e159405625d5314b35036f255e7f43cb6588409" - integrity sha512-N+lSr/UybTnSnvSLjzbAWe600x3DhBj3Lerk/BMllB6wDMzgW6OgNI/5eOGnbBAwY8lxxyHQmv/R5ER35nlVmg== - dependencies: - "@codemirror/state" "^6.1.4" - style-mod "^4.1.0" - w3c-keyname "^2.2.4" - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@discoveryjs/json-ext@0.5.7": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@emotion/babel-plugin@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" - integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/serialize" "^1.1.2" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.2.0" - -"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== - 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" - -"@emotion/hash@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" - integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== - -"@emotion/is-prop-valid@^0.8.8": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" - integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== - dependencies: - "@emotion/memoize" "0.7.4" - -"@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== - dependencies: - "@emotion/memoize" "^0.8.1" - -"@emotion/memoize@0.7.4": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" - integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== - -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== - -"@emotion/react@^11.8.1": - version "11.11.1" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" - integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" - integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== - dependencies: - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/unitless" "^0.8.1" - "@emotion/utils" "^1.2.1" - csstype "^3.0.2" - -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== - -"@emotion/stylis@^0.8.4": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== - -"@emotion/unitless@^0.7.4": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@emotion/unitless@^0.8.0", "@emotion/unitless@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== - -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== - -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== - -"@esbuild/android-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" - integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== - -"@esbuild/android-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" - integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== - -"@esbuild/android-arm64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz#bc35990f412a749e948b792825eef7df0ce0e073" - integrity sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw== - -"@esbuild/android-arm@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" - integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== - -"@esbuild/android-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" - integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== - -"@esbuild/android-arm@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.2.tgz#edd1c8f23ba353c197f5b0337123c58ff2a56999" - integrity sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q== - -"@esbuild/android-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" - integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== - -"@esbuild/android-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" - integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== - -"@esbuild/android-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.2.tgz#2dcdd6e6f1f2d82ea1b746abd8da5b284960f35a" - integrity sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w== - -"@esbuild/darwin-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" - integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== - -"@esbuild/darwin-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" - integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== - -"@esbuild/darwin-arm64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz#55b36bc06d76f5c243987c1f93a11a80d8fc3b26" - integrity sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA== - -"@esbuild/darwin-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" - integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== - -"@esbuild/darwin-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" - integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== - -"@esbuild/darwin-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz#982524af33a6424a3b5cb44bbd52559623ad719c" - integrity sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw== - -"@esbuild/freebsd-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" - integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== - -"@esbuild/freebsd-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" - integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== - -"@esbuild/freebsd-arm64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz#8e478a0856645265fe79eac4b31b52193011ee06" - integrity sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ== - -"@esbuild/freebsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" - integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== - -"@esbuild/freebsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" - integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== - -"@esbuild/freebsd-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz#01b96604f2540db023c73809bb8ae6cd1692d6f3" - integrity sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw== - -"@esbuild/linux-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" - integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== - -"@esbuild/linux-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" - integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== - -"@esbuild/linux-arm64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz#7e5d2c7864c5c83ec789b59c77cd9c20d2594916" - integrity sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg== - -"@esbuild/linux-arm@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" - integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== - -"@esbuild/linux-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" - integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== - -"@esbuild/linux-arm@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz#c32ae97bc0246664a1cfbdb4a98e7b006d7db8ae" - integrity sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg== - -"@esbuild/linux-ia32@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" - integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== - -"@esbuild/linux-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" - integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== - -"@esbuild/linux-ia32@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz#3fc4f0fa026057fe885e4a180b3956e704f1ceaa" - integrity sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ== - -"@esbuild/linux-loong64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" - integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== - -"@esbuild/linux-loong64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" - integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== - -"@esbuild/linux-loong64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz#633bcaea443f3505fb0ed109ab840c99ad3451a4" - integrity sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw== - -"@esbuild/linux-mips64el@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" - integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== - -"@esbuild/linux-mips64el@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" - integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== - -"@esbuild/linux-mips64el@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz#e0bff2898c46f52be7d4dbbcca8b887890805823" - integrity sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg== - -"@esbuild/linux-ppc64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" - integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== - -"@esbuild/linux-ppc64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" - integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== - -"@esbuild/linux-ppc64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz#d75798da391f54a9674f8c143b9a52d1dbfbfdde" - integrity sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw== - -"@esbuild/linux-riscv64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" - integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== - -"@esbuild/linux-riscv64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" - integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== - -"@esbuild/linux-riscv64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz#012409bd489ed1bb9b775541d4a46c5ded8e6dd8" - integrity sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw== - -"@esbuild/linux-s390x@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" - integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== - -"@esbuild/linux-s390x@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" - integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== - -"@esbuild/linux-s390x@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz#ece3ed75c5a150de8a5c110f02e97d315761626b" - integrity sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g== - -"@esbuild/linux-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" - integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== - -"@esbuild/linux-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" - integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== - -"@esbuild/linux-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz#dea187019741602d57aaf189a80abba261fbd2aa" - integrity sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ== - -"@esbuild/netbsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" - integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== - -"@esbuild/netbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" - integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== - -"@esbuild/netbsd-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz#bbfd7cf9ab236a23ee3a41b26f0628c57623d92a" - integrity sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ== - -"@esbuild/openbsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" - integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== - -"@esbuild/openbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" - integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== - -"@esbuild/openbsd-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz#fa5c4c6ee52a360618f00053652e2902e1d7b4a7" - integrity sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw== - -"@esbuild/sunos-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" - integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== - -"@esbuild/sunos-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" - integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== - -"@esbuild/sunos-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz#52a2ac8ac6284c02d25df22bb4cfde26fbddd68d" - integrity sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw== - -"@esbuild/win32-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" - integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== - -"@esbuild/win32-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" - integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== - -"@esbuild/win32-arm64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz#719ed5870855de8537aef8149694a97d03486804" - integrity sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg== - -"@esbuild/win32-ia32@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" - integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== - -"@esbuild/win32-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" - integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== - -"@esbuild/win32-ia32@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz#24832223880b0f581962c8660f8fb8797a1e046a" - integrity sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA== - -"@esbuild/win32-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" - integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== - -"@esbuild/win32-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" - integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== - -"@esbuild/win32-x64@0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz#1205014625790c7ff0e471644a878a65d1e34ab0" - integrity sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw== - -"@floating-ui/core@^1.4.2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" - integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== - dependencies: - "@floating-ui/utils" "^0.1.3" - -"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.5.1": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" - integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== - dependencies: - "@floating-ui/core" "^1.4.2" - "@floating-ui/utils" "^0.1.3" - -"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.2.tgz#fab244d64db08e6bed7be4b5fcce65315ef44d20" - integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== - dependencies: - "@floating-ui/dom" "^1.5.1" - -"@floating-ui/utils@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" - integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== - -"@formatjs/ecma402-abstract@1.14.3": - version "1.14.3" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz#6428f243538a11126180d121ce8d4b2f17465738" - integrity sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg== - dependencies: - "@formatjs/intl-localematcher" "0.2.32" - tslib "^2.4.0" - -"@formatjs/fast-memoize@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz#f15aaa73caad5562899c69bdcad8db82adcd3b0b" - integrity sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA== - dependencies: - tslib "^2.4.0" - -"@formatjs/icu-messageformat-parser@2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.1.tgz#953080ea5c053bc73bdf55d0a524a3c3c133ae6b" - integrity sha512-knF2AkAKN4Upv4oIiKY4Wd/dLH68TNMPgV/tJMu/T6FP9aQwbv8fpj7U3lkyniPaNVxvia56Gxax8MKOjtxLSQ== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/icu-skeleton-parser" "1.3.18" - tslib "^2.4.0" - -"@formatjs/icu-skeleton-parser@1.3.18": - version "1.3.18" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz#7aed3d60e718c8ad6b0e64820be44daa1e29eeeb" - integrity sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - tslib "^2.4.0" - -"@formatjs/intl-displaynames@6.3.1": - version "6.3.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.3.1.tgz#6dcea7cb801460e2a8fa63eb38c54aa1b24f92c0" - integrity sha512-TlxguMDUbnFrJ4NA8fSyqXC62M7czvlRJ5mrJgtB91JVA+QPjjNdcRm1qPIC/DcU/pGUDcEzThn/x5A+jp15gg== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/intl-localematcher" "0.2.32" - tslib "^2.4.0" - -"@formatjs/intl-listformat@7.2.1": - version "7.2.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.2.1.tgz#874eddc7d53ba2e3fd911bf30efc459fc99f08db" - integrity sha512-fRJFWLrGa7d25I4JSxNjKX29oXGcIXx8fJjgURnvs2C3ijS4gurUgFrUwLbv/2KfPfyJ5g567pz2INelNJZBdw== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/intl-localematcher" "0.2.32" - tslib "^2.4.0" - -"@formatjs/intl-localematcher@0.2.32": - version "0.2.32" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz#00d4d307cd7d514b298e15a11a369b86c8933ec1" - integrity sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ== - dependencies: - tslib "^2.4.0" - -"@formatjs/intl@2.7.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.7.1.tgz#f7e052ff09e9fe019ad83d4139af0de40084a2ae" - integrity sha512-se6vxidsN3PCmzqTsDd3YDT4IX9ZySPy39LYhF7x2ssNvlGMOuW3umkrIhKkXB7ZskqsJGY53LVCdiHsSwhGng== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/fast-memoize" "2.0.1" - "@formatjs/icu-messageformat-parser" "2.3.1" - "@formatjs/intl-displaynames" "6.3.1" - "@formatjs/intl-listformat" "7.2.1" - intl-messageformat "10.3.4" - tslib "^2.4.0" - -"@internationalized/date@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.0.tgz#67f1dd62355f05140cc80e324842e9bfb4553abe" - integrity sha512-nw0Q+oRkizBWMioseI8+2TeUPEyopJVz5YxoYVzR0W1v+2YytiYah7s/ot35F149q/xAg4F1gT/6eTd+tsUpFQ== - dependencies: - "@swc/helpers" "^0.5.0" - -"@internationalized/number@^3.3.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.5.0.tgz#9de6018424b441a6545f209afa286ad7df4a2906" - integrity sha512-ZY1BW8HT9WKYvaubbuqXbbDdHhOUMfE2zHHFJeTppid0S+pc8HtdIxFxaYMsGjCb4UsF+MEJ4n2TfU7iHnUK8w== - dependencies: - "@swc/helpers" "^0.5.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@juggle/resize-observer@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" - integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== - -"@koa/cors@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb" - integrity sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw== - dependencies: - vary "^1.1.2" - -"@koa/router@10.1.1": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@koa/router/-/router-10.1.1.tgz#8e5a85c9b243e0bc776802c0de564561e57a5f78" - integrity sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw== - dependencies: - debug "^4.1.1" - http-errors "^1.7.3" - koa-compose "^4.1.0" - methods "^1.1.2" - path-to-regexp "^6.1.0" - -"@lezer/common@^1.0.0", "@lezer/common@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.1.0.tgz#2e5bfe01d7a2ada6056d93c677bba4f1495e098a" - integrity sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw== - -"@lezer/highlight@^1.0.0": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.1.6.tgz#87e56468c0f43c2a8b3dc7f0b7c2804b34901556" - integrity sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg== - dependencies: - "@lezer/common" "^1.0.0" - -"@lezer/json@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@lezer/json/-/json-1.0.1.tgz#3bf5641f3d1408ec31a5f9b29e4e96c6e3a232e6" - integrity sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw== - dependencies: - "@lezer/highlight" "^1.0.0" - "@lezer/lr" "^1.0.0" - -"@lezer/lr@^1.0.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.3.11.tgz#67647b3af77bbd798cabc726b7a8151ff9b6d64b" - integrity sha512-W7IZXXyi6BfVredTDk3jHe1V6zUcdjRcUlvTsrWGOvIOU2eg3sfEDtTDFHo1TRxZhtQGX1EyHHUXoXvJNSxcnA== - dependencies: - "@lezer/common" "^1.0.0" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@one-ini/wasm@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" - integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== - -"@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" - -"@pmmmwh/react-refresh-webpack-plugin@0.5.10": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8" - integrity sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA== - dependencies: - ansi-html-community "^0.0.8" - common-path-prefix "^3.0.0" - core-js-pure "^3.23.3" - error-stack-parser "^2.0.6" - find-up "^5.0.0" - html-entities "^2.1.0" - loader-utils "^2.0.4" - schema-utils "^3.0.0" - source-map "^0.7.3" - -"@pnpm/config.env-replace@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" - integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== - -"@pnpm/network.ca-file@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" - integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== - dependencies: - graceful-fs "4.2.10" - -"@pnpm/npm-conf@^2.1.0": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz#0058baf1c26cbb63a828f0193795401684ac86f0" - integrity sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA== - dependencies: - "@pnpm/config.env-replace" "^1.1.0" - "@pnpm/network.ca-file" "^1.0.1" - config-chain "^1.1.11" - -"@polka/url@^1.0.0-next.24": - version "1.0.0-next.24" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" - integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== - -"@radix-ui/number@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674" - integrity sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/primitive@1.0.1", "@radix-ui/primitive@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" - integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-arrow@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" - integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" - -"@radix-ui/react-collection@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" - integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-slot" "1.0.2" - -"@radix-ui/react-compose-refs@1.0.1", "@radix-ui/react-compose-refs@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" - integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-context@1.0.1", "@radix-ui/react-context@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" - integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-direction@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" - integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-dismissable-layer@1.0.5", "@radix-ui/react-dismissable-layer@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" - integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-escape-keydown" "1.0.3" - -"@radix-ui/react-dropdown-menu@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63" - integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-menu" "2.0.6" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-controllable-state" "1.0.1" - -"@radix-ui/react-focus-guards@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" - integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-focus-scope@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" - integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - -"@radix-ui/react-id@1.0.1", "@radix-ui/react-id@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" - integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-layout-effect" "1.0.1" - -"@radix-ui/react-menu@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" - integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-dismissable-layer" "1.0.5" - "@radix-ui/react-focus-guards" "1.0.1" - "@radix-ui/react-focus-scope" "1.0.4" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-popper" "1.1.3" - "@radix-ui/react-portal" "1.0.4" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-roving-focus" "1.0.4" - "@radix-ui/react-slot" "1.0.2" - "@radix-ui/react-use-callback-ref" "1.0.1" - aria-hidden "^1.1.1" - react-remove-scroll "2.5.5" - -"@radix-ui/react-popper@1.1.3", "@radix-ui/react-popper@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" - integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== - dependencies: - "@babel/runtime" "^7.13.10" - "@floating-ui/react-dom" "^2.0.0" - "@radix-ui/react-arrow" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" - "@radix-ui/react-use-rect" "1.0.1" - "@radix-ui/react-use-size" "1.0.1" - "@radix-ui/rect" "1.0.1" - -"@radix-ui/react-portal@1.0.4", "@radix-ui/react-portal@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" - integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" - -"@radix-ui/react-presence@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" - integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" - -"@radix-ui/react-primitive@1.0.3", "@radix-ui/react-primitive@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" - integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-slot" "1.0.2" - -"@radix-ui/react-roving-focus@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" - integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-controllable-state" "1.0.1" - -"@radix-ui/react-separator@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.3.tgz#be5a931a543d5726336b112f465f58585c04c8aa" - integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" - -"@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" - integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - -"@radix-ui/react-toggle-group@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz#f5b5c8c477831b013bec3580c55e20a68179d6ec" - integrity sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-roving-focus" "1.0.4" - "@radix-ui/react-toggle" "1.0.3" - "@radix-ui/react-use-controllable-state" "1.0.1" - -"@radix-ui/react-toggle@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz#aecb2945630d1dc5c512997556c57aba894e539e" - integrity sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-controllable-state" "1.0.1" - -"@radix-ui/react-toolbar@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-toolbar/-/react-toolbar-1.0.4.tgz#3211a105567fa016e89921b5b514877f833de559" - integrity sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-roving-focus" "1.0.4" - "@radix-ui/react-separator" "1.0.3" - "@radix-ui/react-toggle-group" "1.0.4" - -"@radix-ui/react-use-callback-ref@1.0.1", "@radix-ui/react-use-callback-ref@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" - integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-controllable-state@1.0.1", "@radix-ui/react-use-controllable-state@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" - integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-callback-ref" "1.0.1" - -"@radix-ui/react-use-escape-keydown@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" - integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-callback-ref" "1.0.1" - -"@radix-ui/react-use-layout-effect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" - integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-previous@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz#b595c087b07317a4f143696c6a01de43b0d0ec66" - integrity sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-rect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" - integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/rect" "1.0.1" - -"@radix-ui/react-use-size@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" - integrity sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-layout-effect" "1.0.1" - -"@radix-ui/react-visually-hidden@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" - integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" - -"@radix-ui/rect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" - integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@react-dnd/asap@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.1.tgz#5291850a6b58ce6f2da25352a64f1b0674871aab" - integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg== - -"@react-dnd/invariant@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-3.0.1.tgz#7e70be19ea21b539e8bf1da28466f4f05df2a4cc" - integrity sha512-blqduwV86oiKw2Gr44wbe3pj3Z/OsXirc7ybCv9F/pLAR+Aih8F3rjeJzK0ANgtYKv5lCpkGVoZAeKitKDaD/g== - -"@react-dnd/shallowequal@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-3.0.1.tgz#8056fe046a8d10a275e321ec0557ae652d7a4d06" - integrity sha512-XjDVbs3ZU16CO1h5Q3Ew2RPJqmZBDE/EVf1LYp6ePEffs3V/MX9ZbL5bJr8qiK5SbGmUMuDoaFgyKacYz8prRA== - -"@react-email/render@0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@react-email/render/-/render-0.0.5.tgz#c71abe472dad7658af34facbac138646d5579691" - integrity sha512-EE9mCvR3lXeZEJaldCEaEc4msCwPQwZfXbhuPVl3SpRsiHiMK0wNm2X39vVpqzCzxrm0wljCoLruT7Klp9DZAw== - dependencies: - html-to-text "9.0.3" - pretty "2.0.0" - react "18.2.0" - react-dom "18.2.0" - -"@reduxjs/toolkit@1.9.7": - version "1.9.7" - resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.7.tgz#7fc07c0b0ebec52043f8cb43510cf346405f78a6" - integrity sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ== - dependencies: - immer "^9.0.21" - redux "^4.2.1" - redux-thunk "^2.4.2" - reselect "^4.1.8" - -"@reinmar/ckeditor5-maximum-length@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@reinmar/ckeditor5-maximum-length/-/ckeditor5-maximum-length-0.0.1.tgz#6097a7d73fa2a5b2b4ca61212459225b84f59ee5" - integrity sha512-w03YHqYPWFzwCc3OzrwfwL1NkL9KQ194yiEdQ0YWNdm0PBkoZVlSiVyfShe0TwxoJlgbuRvPjPBhUR3q1tUtkA== - dependencies: - ckeditor5 ">=35.1.0" - -"@rushstack/ts-command-line@^4.12.2": - version "4.16.0" - resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.16.0.tgz#92d3af993ca5ee271ea130d41f5ce6a9479cae3f" - integrity sha512-WJKhdR9ThK9Iy7t78O3at7I3X4Ssp5RRZay/IQa8NywqkFy/DQbT3iLouodMMdUwLZD9n8n++xLubVd3dkmpkg== - dependencies: - "@types/argparse" "1.0.38" - argparse "~1.0.9" - colors "~1.2.1" - string-argv "~0.3.1" - -"@selderee/plugin-htmlparser2@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.10.0.tgz#8a304d18df907e086f3cfc71ea0ced52d6524430" - integrity sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA== - dependencies: - domhandler "^5.0.3" - selderee "^0.10.0" - -"@sentry/core@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.7.tgz#156aaa56dd7fad8c89c145be6ad7a4f7209f9785" - integrity sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw== - dependencies: - "@sentry/hub" "6.19.7" - "@sentry/minimal" "6.19.7" - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" - tslib "^1.9.3" - -"@sentry/hub@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.7.tgz#58ad7776bbd31e9596a8ec46365b45cd8b9cfd11" - integrity sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA== - dependencies: - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" - tslib "^1.9.3" - -"@sentry/minimal@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.19.7.tgz#b3ee46d6abef9ef3dd4837ebcb6bdfd01b9aa7b4" - integrity sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ== - dependencies: - "@sentry/hub" "6.19.7" - "@sentry/types" "6.19.7" - tslib "^1.9.3" - -"@sentry/node@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.19.7.tgz#32963b36b48daebbd559e6f13b1deb2415448592" - integrity sha512-gtmRC4dAXKODMpHXKfrkfvyBL3cI8y64vEi3fDD046uqYcrWdgoQsffuBbxMAizc6Ez1ia+f0Flue6p15Qaltg== - dependencies: - "@sentry/core" "6.19.7" - "@sentry/hub" "6.19.7" - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" - cookie "^0.4.1" - https-proxy-agent "^5.0.0" - lru_map "^0.3.3" - tslib "^1.9.3" - -"@sentry/types@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7" - integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg== - -"@sentry/utils@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79" - integrity sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA== - dependencies: - "@sentry/types" "6.19.7" - tslib "^1.9.3" - -"@simov/deep-extend@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@simov/deep-extend/-/deep-extend-1.0.0.tgz#dff17d38305614e296eb80bf4898b9d10b061325" - integrity sha512-Arv8/ZPcdKAMJnNF8cks35mPq1y3JnwH1lWpfWDKlJoj+Vw2xmA4+oL7m9GVHTgdX0mGFR7bCPTBTGbxhnfJJw== - -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - -"@sindresorhus/slugify@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/slugify/-/slugify-1.1.0.tgz#2f195365d9b953384305b62664b44b4036c49430" - integrity sha512-ujZRbmmizX26yS/HnB3P9QNlNa4+UvHh+rIse3RbOXLp8yl6n1TxB4t7NHggtVgS8QmmOtzXo48kCxZGACpkPw== - dependencies: - "@sindresorhus/transliterate" "^0.1.1" - escape-string-regexp "^4.0.0" - -"@sindresorhus/transliterate@^0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz#ffce368271d153550e87de81486004f2637425af" - integrity sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w== - dependencies: - escape-string-regexp "^2.0.0" - lodash.deburr "^4.1.0" - -"@strapi/admin@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/admin/-/admin-4.16.2.tgz#9bc7445b8c84db9ffb782185f5e20223eca8998b" - integrity sha512-kmz7QBGJcSjMdSA7jL3D9cj/xyZYPBiIqJFMSwMuc2P4Mr0QK1lKX3I6SCSMjLjEzjDi88vZEpuz0WUNegKQfQ== - dependencies: - "@casl/ability" "6.5.0" - "@pmmmwh/react-refresh-webpack-plugin" "0.5.10" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-toolbar" "1.0.4" - "@reduxjs/toolkit" "1.9.7" - "@strapi/data-transfer" "4.16.2" - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/permissions" "4.16.2" - "@strapi/provider-audit-logs-local" "4.16.2" - "@strapi/types" "4.16.2" - "@strapi/typescript-utils" "4.16.2" - "@strapi/utils" "4.16.2" - axios "1.6.0" - bcryptjs "2.4.3" - boxen "5.1.2" - browserslist "^4.22.1" - browserslist-to-esbuild "1.2.0" - chalk "^4.1.2" - chokidar "3.5.3" - codemirror5 "npm:codemirror@^5.65.11" - cross-env "^7.0.3" - css-loader "^6.8.1" - date-fns "2.30.0" - dotenv "14.2.0" - esbuild "0.19.2" - esbuild-loader "^2.21.0" - esbuild-register "3.5.0" - execa "5.1.1" - fast-deep-equal "3.1.3" - find-root "1.1.0" - fork-ts-checker-webpack-plugin "8.0.0" - formik "2.4.0" - fractional-indexing "3.2.0" - fs-extra "10.0.0" - highlight.js "^10.4.1" - history "^4.9.0" - html-webpack-plugin "5.5.0" - immer "9.0.19" - inquirer "8.2.5" - invariant "^2.2.4" - js-cookie "2.2.1" - jsonwebtoken "9.0.0" - koa-compose "4.1.0" - koa-passport "5.0.0" - koa-static "5.0.0" - koa2-ratelimit "^1.1.2" - lodash "4.17.21" - markdown-it "^12.3.2" - markdown-it-abbr "^1.0.4" - markdown-it-container "^3.0.0" - markdown-it-deflist "^2.1.0" - markdown-it-emoji "^2.0.0" - markdown-it-footnote "^3.0.3" - markdown-it-ins "^3.0.1" - markdown-it-mark "^3.0.1" - markdown-it-sub "^1.0.0" - markdown-it-sup "1.0.0" - mini-css-extract-plugin "2.7.6" - node-schedule "2.1.0" - ora "5.4.1" - outdent "0.8.0" - p-map "4.0.0" - passport-local "1.0.0" - pluralize "8.0.0" - prettier "2.8.4" - prop-types "^15.8.1" - qs "6.11.1" - react "^18.2.0" - react-dnd "15.1.2" - react-dnd-html5-backend "15.1.3" - react-dom "^18.2.0" - react-error-boundary "3.1.4" - react-helmet "^6.1.0" - react-intl "6.4.1" - react-is "^18.2.0" - react-query "3.39.3" - react-redux "8.1.1" - react-refresh "0.14.0" - react-router-dom "5.3.4" - react-select "5.7.0" - react-window "1.8.8" - read-pkg-up "7.0.1" - resolve-from "5.0.0" - rimraf "3.0.2" - sanitize-html "2.11.0" - semver "7.5.4" - sift "16.0.1" - slate "0.94.1" - slate-history "0.93.0" - slate-react "0.98.3" - style-loader "3.3.1" - styled-components "5.3.3" - typescript "5.2.2" - webpack "^5.88.1" - webpack-bundle-analyzer "^4.9.0" - webpack-dev-middleware "6.1.1" - webpack-hot-middleware "2.25.4" - yup "0.32.9" - -"@strapi/content-releases@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/content-releases/-/content-releases-4.16.2.tgz#1adf428086ea4d6ded17c7b6a3864180de43ad53" - integrity sha512-m2FYLY9jkeoVdFdISLHM80eKaLrAj+1LWOCneyeunIozHP/u9NnCxPv/j9QmTItxm1C+hkYELZSz4b6i3Nq4iQ== - dependencies: - "@reduxjs/toolkit" "1.9.7" - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/types" "4.16.2" - "@strapi/utils" "4.16.2" - axios "1.6.0" - formik "2.4.0" - react-intl "6.4.1" - react-redux "8.1.1" - yup "0.32.9" - -"@strapi/data-transfer@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/data-transfer/-/data-transfer-4.16.2.tgz#850067f006ce3d0d85c1a0aee782e6e6c7f51815" - integrity sha512-CDJQ6Kb1H/y25zWAsdXQw9FU/F6iRYdef1dVUh0yMLZNS8HrCWAPu/jjLwrrLF6KKD8O30jMIsH1Nsdr/J5tnw== - dependencies: - "@strapi/logger" "4.16.2" - "@strapi/strapi" "4.16.2" - "@strapi/types" "4.16.2" - "@strapi/utils" "4.16.2" - chalk "4.1.2" - cli-table3 "0.6.2" - commander "8.3.0" - fs-extra "10.0.0" - inquirer "8.2.5" - lodash "4.17.21" - ora "5.4.1" - resolve-cwd "3.0.0" - semver "7.5.4" - stream-chain "2.2.5" - stream-json "1.8.0" - tar "6.1.13" - tar-stream "2.2.0" - ws "8.13.0" - -"@strapi/database@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/database/-/database-4.16.2.tgz#1565130b52cb8e01960df9f3e7cd214e9ca717b5" - integrity sha512-Vq54I7vlAix/XR17mBYmxGxS8R1eXDYHhbjqlyPRMJpbG3PPvOYyEVesy6caQHjivjL/LQNmmDXoFSd7qUzOgQ== - dependencies: - "@strapi/utils" "4.16.2" - date-fns "2.30.0" - debug "4.3.4" - fs-extra "10.0.0" - knex "2.5.0" - lodash "4.17.21" - semver "7.5.4" - umzug "3.2.1" - -"@strapi/design-system@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@strapi/design-system/-/design-system-1.13.1.tgz#42d1c7b054e3d0f19ba707b5f2e10cc0d2eba280" - integrity sha512-mtfONSTZmOKd0Mvb1NGEYx5qaA4uDPqx5q3/67kDSK2mkfjEe3j69SoQ6ONt/glvsidclWtez6R1aJmF5cojRg== - dependencies: - "@codemirror/lang-json" "^6.0.1" - "@floating-ui/react-dom" "^2.0.2" - "@internationalized/date" "^3.5.0" - "@internationalized/number" "^3.3.0" - "@radix-ui/react-dismissable-layer" "^1.0.5" - "@radix-ui/react-dropdown-menu" "^2.0.6" - "@radix-ui/react-focus-scope" "1.0.4" - "@strapi/ui-primitives" "^1.13.1" - "@uiw/react-codemirror" "^4.21.20" - aria-hidden "^1.2.3" - compute-scroll-into-view "^3.1.0" - prop-types "^15.8.1" - react-remove-scroll "^2.5.7" - -"@strapi/generate-new@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/generate-new/-/generate-new-4.16.2.tgz#6d885be8d4137a19184842e4855f7cd43bc1fc96" - integrity sha512-bLTsS73wqjgYvQeCVcxhodG+fssjTnYcCvVHyBBzp28Gv8AoBaiUaVgVPedHGA4XpfNl1y0zQZXN2B2rrrhNjQ== - dependencies: - "@sentry/node" "6.19.7" - chalk "^4.1.2" - execa "5.1.1" - fs-extra "10.0.0" - inquirer "8.2.5" - lodash "4.17.21" - node-fetch "2.7.0" - node-machine-id "^1.1.10" - ora "^5.4.1" - semver "7.5.4" - tar "6.1.13" - -"@strapi/generators@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/generators/-/generators-4.16.2.tgz#d2d9da944f7041fcc2887294abe77be7fa73834b" - integrity sha512-X4ibD/+AokFvhDtaTApQtyli3NEUkbqxhqexCQPhgWa5H+8zEpQHro8tTMHcW8l4to1EYBt2gLnVb6eh/M1Tzg== - dependencies: - "@sindresorhus/slugify" "1.1.0" - "@strapi/typescript-utils" "4.16.2" - "@strapi/utils" "4.16.2" - chalk "4.1.2" - copyfiles "2.4.1" - fs-extra "10.0.0" - node-plop "0.26.3" - plop "2.7.6" - pluralize "8.0.0" - -"@strapi/helper-plugin@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/helper-plugin/-/helper-plugin-4.16.2.tgz#c9ed341965c1aba51b5adb7e413c91cac00b869e" - integrity sha512-O9ZWm/qHa2gZByuG4+YePvy9wfHykh4vUw7wufIWn4aTdv3AQdCIBmN33iiDXKd9Fxud+o7XIubXe2ZRTZbySQ== - dependencies: - axios "1.6.0" - date-fns "2.30.0" - formik "2.4.0" - immer "9.0.19" - lodash "4.17.21" - qs "6.11.1" - react-helmet "6.1.0" - react-intl "6.4.1" - react-query "3.39.3" - react-select "5.7.0" - -"@strapi/icons@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-1.13.0.tgz#0020560b5bed008ddc39aa177b264cb3bebe8b67" - integrity sha512-bmFJvyM75nuVyJVq4bgHxRYwu9eEkmRKNipEb/GBTBvvkHGN0GTHLOAedGpKGvB7RwcclbaymOP7oBlxahOmsw== - -"@strapi/logger@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/logger/-/logger-4.16.2.tgz#3e9ddd2b050994491c8d38e2acef13f675f74383" - integrity sha512-9BBU8AgQVjlM/uyCFW+tUlyVCcfQ7rlRHtGQr+ZFtHY7Thzd/vRHxvC3i34Cah7qAn8QU5aemNQJvGxH04mz+w== - dependencies: - lodash "4.17.21" - winston "3.10.0" - -"@strapi/pack-up@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/pack-up/-/pack-up-4.16.2.tgz#66a6a9c7136dda54d08de9f16a1cf0421c63d099" - integrity sha512-jZNwDOQTK+wXMNCyJIzumW8w7BivCG4B1LK+jyYXTomwKnwkvLLGvJZtUdgY52a5ogJyLHKaecKhbskIuT/Fsg== - dependencies: - "@vitejs/plugin-react" "4.1.0" - boxen "5.1.2" - browserslist-to-esbuild "1.2.0" - chalk "4.1.2" - chokidar "3.5.3" - commander "8.3.0" - esbuild "0.19.2" - esbuild-register "3.5.0" - get-latest-version "5.1.0" - git-url-parse "13.1.0" - ini "4.1.1" - ora "5.4.1" - outdent "0.8.0" - pkg-up "3.1.0" - prettier "2.8.4" - prettier-plugin-packagejson "2.4.5" - prompts "2.4.2" - rxjs "7.8.1" - typescript "5.2.2" - vite "4.4.9" - yup "0.32.9" - -"@strapi/permissions@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/permissions/-/permissions-4.16.2.tgz#52bde680871017d0a1809e91323cd1a2196a6767" - integrity sha512-/vew0jV8WGXOL4DXUJrCiaCpwxVtDjMkwnAzTnkJmZQVGUu38VP+Uj2frcZpOewumCE8/k1kAGnQRMRhYmc/eA== - dependencies: - "@casl/ability" "6.5.0" - "@strapi/utils" "4.16.2" - lodash "4.17.21" - qs "6.11.1" - sift "16.0.1" - -"@strapi/plugin-content-manager@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-content-manager/-/plugin-content-manager-4.16.2.tgz#d35ee4e8c046c74710bfb9c83d9bf3480d95f206" - integrity sha512-dpd7VwbLar0jOB1rCNJV5fyMsl/BPOEk120bhFQ45wPYcKpf/6daBklNklVgnho/8fN90cLTMzjjHET5LmabFQ== - dependencies: - "@sindresorhus/slugify" "1.1.0" - "@strapi/utils" "4.16.2" - lodash "4.17.21" - qs "6.11.1" - -"@strapi/plugin-content-type-builder@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-content-type-builder/-/plugin-content-type-builder-4.16.2.tgz#591f4c83a0a2d970293b92d6846ca0f228e098b0" - integrity sha512-Xc/iFuJCerDEvpybya7xiuX0zOe/jKkFwzAf1LahKzAEk+GIiAYt1sd+AiN8iUiDIyh4CH7L+iPM3Dw5khV+Pw== - dependencies: - "@sindresorhus/slugify" "1.1.0" - "@strapi/design-system" "1.13.1" - "@strapi/generators" "4.16.2" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/utils" "4.16.2" - fs-extra "10.0.0" - immer "9.0.19" - lodash "4.17.21" - pluralize "8.0.0" - prop-types "^15.8.1" - qs "6.11.1" - react-helmet "^6.1.0" - react-intl "6.4.1" - react-redux "8.1.1" - yup "0.32.9" - -"@strapi/plugin-email@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-email/-/plugin-email-4.16.2.tgz#23f2ad67c38bd0b9658a32b9d37d08aeaf6fcfac" - integrity sha512-WSi0k36JasovwlKbTC1uHlSe60TbX5FmVvbHDIQE7sWERoGJLFLYOetZ8HN0Qy/f1x07ltcNy4kGj9KL/O6eLw== - dependencies: - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/provider-email-sendmail" "4.16.2" - "@strapi/utils" "4.16.2" - lodash "4.17.21" - prop-types "^15.8.1" - react-intl "6.4.1" - react-query "3.39.3" - yup "0.32.9" - -"@strapi/plugin-i18n@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-i18n/-/plugin-i18n-4.16.2.tgz#7f326515650e210885e644cf7558a19e50690efc" - integrity sha512-wHhgN4qWQ39L6Xn+02JZ2URojYlsZmDofAiRjYtTEcTrN5iTH2Gt9yC4OY4VHTOwwz3QMpZzYlY2lv9zvFvFJg== - dependencies: - "@reduxjs/toolkit" "1.9.7" - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/utils" "4.16.2" - formik "2.4.0" - immer "9.0.19" - lodash "4.17.21" - prop-types "^15.8.1" - qs "6.11.1" - react-intl "6.4.1" - react-query "3.39.3" - react-redux "8.1.1" - yup "0.32.9" - -"@strapi/plugin-seo@1.9.6": - version "1.9.6" - resolved "https://registry.yarnpkg.com/@strapi/plugin-seo/-/plugin-seo-1.9.6.tgz#e23a7206a47e6b96c616ce4eae5bd900e9624139" - integrity sha512-gqsZSNWgTTLp0eQMp9oXzSseOcs/S7Kz03CSo2nT1sXSPwybgF9gE4gedzlBFYErA7XbyDZb9ZIWpxx5jRTA4w== - dependencies: - date-fns "^2.29.3" - eslint-plugin-react-hooks "^4.3.0" - lodash "^4.17.21" - showdown "^1.9.1" - styled-components "^6.0.7" - -"@strapi/plugin-upload@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-upload/-/plugin-upload-4.16.2.tgz#eb08935619b1662ea06fd529d9807db59eb23784" - integrity sha512-Yj0R2iY0ZqXXcjoLKNVO8SAdw26TKnHF1QuzLojTu7vZKK9hd1VOKISRDWJ3zV/gq8JuttE7ientjyd36ayftA== - dependencies: - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/provider-upload-local" "4.16.2" - "@strapi/utils" "4.16.2" - axios "1.6.0" - byte-size "7.0.1" - cropperjs "1.6.0" - date-fns "2.30.0" - formik "2.4.0" - fs-extra "10.0.0" - immer "9.0.19" - koa-range "0.3.0" - koa-static "5.0.0" - lodash "4.17.21" - mime-types "2.1.35" - prop-types "^15.8.1" - qs "6.11.1" - react-dnd "15.1.2" - react-helmet "^6.1.0" - react-intl "6.4.1" - react-query "3.39.3" - react-redux "8.1.1" - react-select "5.7.0" - sharp "0.32.6" - yup "0.32.9" - -"@strapi/plugin-users-permissions@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/plugin-users-permissions/-/plugin-users-permissions-4.16.2.tgz#ca20b1850253be0e1250603790c364eb6354b61e" - integrity sha512-27QZSKW1WpbKYt5I/+AkiIh9o6DDQE2nHilfln6bSaW5s3htM+tFR7vdTTjbHOhpMTD6kpqLFFhKVFoDcUP1Og== - dependencies: - "@strapi/design-system" "1.13.1" - "@strapi/helper-plugin" "4.16.2" - "@strapi/icons" "1.13.0" - "@strapi/utils" "4.16.2" - bcryptjs "2.4.3" - formik "2.4.0" - grant-koa "5.4.8" - immer "9.0.19" - jsonwebtoken "9.0.0" - jwk-to-pem "2.0.5" - koa "2.13.4" - koa2-ratelimit "^1.1.2" - lodash "4.17.21" - prop-types "^15.8.1" - purest "4.0.2" - react-intl "6.4.1" - react-query "3.39.3" - react-redux "8.1.1" - url-join "4.0.1" - yup "0.32.9" - -"@strapi/provider-audit-logs-local@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/provider-audit-logs-local/-/provider-audit-logs-local-4.16.2.tgz#8ae3f99a29cd2eedb5ab4e7761619bd91ed4b04c" - integrity sha512-BAPX3v/EMlJVbuu0Ub7FkB0Pusl4pgVH0xujmxUeUeUnR9d4+Lkwjf4xJbr/6FSo9E+kVgHJrwVyhCg/pflIiQ== - -"@strapi/provider-email-sendmail@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/provider-email-sendmail/-/provider-email-sendmail-4.16.2.tgz#c0019229acb4be9673c4db123d98bc5ea3c2a56e" - integrity sha512-cRol7bABMawuwsf9CEqxS9fZVZR1VHUE64D0kXslVz/IviK/LixoO57F66BZlZ4mi8V7SS+L8jwPN6qRlOsxOA== - dependencies: - "@strapi/utils" "4.16.2" - sendmail "^1.6.1" - -"@strapi/provider-upload-aws-s3@4.14.5": - version "4.14.5" - resolved "https://registry.yarnpkg.com/@strapi/provider-upload-aws-s3/-/provider-upload-aws-s3-4.14.5.tgz#6d4dcc1932573b9ee5c0bad3f2a01e74d7255d95" - integrity sha512-xCiXgFHQUihQwS5OAUgVvt4H2UtLfcy6cJk0Ml2bmnw7F63C6ZWEpxROGdMny/BawUYpkcbL2nNvwy+eHM1u+g== - dependencies: - aws-sdk "2.1472.0" - lodash "4.17.21" - -"@strapi/provider-upload-local@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/provider-upload-local/-/provider-upload-local-4.16.2.tgz#4ba7d14db9e22357f8c0941692aa96cc366ea15b" - integrity sha512-fYUbAQxH3bHwKyA+36+0YBklL6mW/UG8lKrCKmA2cpHCTTIOU2VNnNSrfhIipaQaWsTf7/ZNYfgstRjuHjTayQ== - dependencies: - "@strapi/utils" "4.16.2" - fs-extra "10.0.0" - -"@strapi/strapi@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/strapi/-/strapi-4.16.2.tgz#43fc5ef10d9a665f749e2e687ff675cc40c001ad" - integrity sha512-ARYewyc6eL3LObR60XLFVTNEJJLt97CKvYlMBaE2PUEMYwcOYZEmhDng5fHNcEIIg+3vXQV7KKj4lTw+uFBQ1g== - dependencies: - "@koa/cors" "3.4.3" - "@koa/router" "10.1.1" - "@strapi/admin" "4.16.2" - "@strapi/content-releases" "4.16.2" - "@strapi/data-transfer" "4.16.2" - "@strapi/database" "4.16.2" - "@strapi/generate-new" "4.16.2" - "@strapi/generators" "4.16.2" - "@strapi/logger" "4.16.2" - "@strapi/pack-up" "4.16.2" - "@strapi/permissions" "4.16.2" - "@strapi/plugin-content-manager" "4.16.2" - "@strapi/plugin-content-type-builder" "4.16.2" - "@strapi/plugin-email" "4.16.2" - "@strapi/plugin-upload" "4.16.2" - "@strapi/types" "4.16.2" - "@strapi/typescript-utils" "4.16.2" - "@strapi/utils" "4.16.2" - bcryptjs "2.4.3" - boxen "5.1.2" - chalk "4.1.2" - ci-info "3.8.0" - cli-table3 "0.6.2" - commander "8.3.0" - configstore "5.0.1" - copyfiles "2.4.1" - debug "4.3.4" - delegates "1.0.0" - dotenv "14.2.0" - execa "5.1.1" - fs-extra "10.0.0" - glob "7.2.3" - http-errors "1.8.1" - https-proxy-agent "5.0.1" - inquirer "8.2.5" - is-docker "2.2.1" - koa "2.13.4" - koa-body "4.2.0" - koa-compose "4.1.0" - koa-compress "5.1.0" - koa-favicon "2.1.0" - koa-helmet "7.0.2" - koa-ip "^2.1.2" - koa-session "6.4.0" - koa-static "5.0.0" - lodash "4.17.21" - mime-types "2.1.35" - node-fetch "2.7.0" - node-machine-id "1.1.12" - node-schedule "2.1.0" - open "8.4.0" - ora "5.4.1" - package-json "7.0.0" - pkg-up "3.1.0" - qs "6.11.1" - semver "7.5.4" - statuses "2.0.1" - typescript "5.2.2" - yup "0.32.9" - -"@strapi/types@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/types/-/types-4.16.2.tgz#94c9e228b0fd835ac20387c8827b3eac15576cd3" - integrity sha512-pEi4zQybOL2PRds+4sFmoEr0DQlQxP4brlmYfI8VDQiC5RR/SfNkEExcPeZ8cvIUpYKUGrzpUExZ/QLxcbRUEg== - dependencies: - "@koa/cors" "3.4.3" - "@koa/router" "10.1.1" - "@strapi/database" "4.16.2" - "@strapi/logger" "4.16.2" - "@strapi/permissions" "4.16.2" - "@strapi/utils" "4.16.2" - commander "8.3.0" - https-proxy-agent "5.0.1" - koa "2.13.4" - node-fetch "2.7.0" - node-schedule "2.1.0" - -"@strapi/typescript-utils@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/typescript-utils/-/typescript-utils-4.16.2.tgz#92e1fa8eb3e26c104dc705da21b37bcbf3465125" - integrity sha512-p8E4xvp8T5ZMQ9NNIsdgM5r31TrFbzHaDWbODOoPfvazUWtL5WEts/rmwS58HX6IYa+unuFgsYdECiosyoq3XQ== - dependencies: - chalk "4.1.2" - cli-table3 "0.6.2" - fs-extra "10.0.0" - lodash "4.17.21" - prettier "2.8.4" - typescript "5.2.2" - -"@strapi/ui-primitives@^1.13.1": - version "1.14.0" - resolved "https://registry.yarnpkg.com/@strapi/ui-primitives/-/ui-primitives-1.14.0.tgz#98ff668701b4100bee8bc0212b392158c88744f7" - integrity sha512-M5RhM7/qVuu4gPvHWiSTOdI7bVDWK68aB+XyB/g1hGPqXL2Umsz8Iwn9bJPQk6YwCaHdrobaoB7lEDlfrlUAVA== - dependencies: - "@radix-ui/number" "^1.0.1" - "@radix-ui/primitive" "^1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "^1.0.1" - "@radix-ui/react-context" "^1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-dismissable-layer" "^1.0.5" - "@radix-ui/react-focus-guards" "1.0.1" - "@radix-ui/react-focus-scope" "1.0.4" - "@radix-ui/react-id" "^1.0.1" - "@radix-ui/react-popper" "^1.1.3" - "@radix-ui/react-portal" "^1.0.4" - "@radix-ui/react-primitive" "^1.0.3" - "@radix-ui/react-slot" "^1.0.2" - "@radix-ui/react-use-callback-ref" "^1.0.1" - "@radix-ui/react-use-controllable-state" "^1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" - "@radix-ui/react-use-previous" "^1.0.1" - "@radix-ui/react-visually-hidden" "^1.0.3" - aria-hidden "^1.2.3" - react-remove-scroll "^2.5.7" - -"@strapi/utils@4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@strapi/utils/-/utils-4.16.2.tgz#278b953872645e37ab46369f508bbe8c11ef0f02" - integrity sha512-CuAXBXXke4j0OTVuQswZ8c/Mpeb9W1DhcBhYQpc8Cwn9NeII5VGE57TrIkXL3FCU8q4fcL3IrSLaftmL6/e3uQ== - dependencies: - "@sindresorhus/slugify" "1.1.0" - date-fns "2.30.0" - http-errors "1.8.1" - lodash "4.17.21" - p-map "4.0.0" - yup "0.32.9" - -"@swc/helpers@^0.5.0": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d" - integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw== - dependencies: - tslib "^2.4.0" - -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== - dependencies: - defer-to-connect "^2.0.0" - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@types/argparse@1.0.38": - version "1.0.38" - resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" - integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== - -"@types/babel__core@^7.20.2": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778" - integrity sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.6" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.6.tgz#676f89f67dc8ddaae923f70ebc5f1fa800c031a8" - integrity sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.3.tgz#db9ac539a2fe05cfe9e168b24f360701bde41f5f" - integrity sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.3.tgz#a971aa47441b28ef17884ff945d0551265a2d058" - integrity sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw== - dependencies: - "@babel/types" "^7.20.7" - -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a" - integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - -"@types/fined@*": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@types/fined/-/fined-1.1.3.tgz#83f03e8f0a8d3673dfcafb18fce3571f6250e1bc" - integrity sha512-CWYnSRnun3CGbt6taXeVo2lCbuaj4mchVJ4UF/BdU5TSuIn3AmS13pGMwCsBUoehGbhZrBrpNJZSZI5EVilXww== - -"@types/formidable@^1.0.31": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.2.6.tgz#eee56eca2bd7108c6f1e4dfc9ef10493b9c16543" - integrity sha512-9xwITWH5ok4MrALa7qnUd3McKrvEn5iUZM5/m0AJjOo/sMPUISzuBK/qAHHMV9t5ShjG4fjr0VEm8J+szAKDWA== - dependencies: - "@types/node" "*" - -"@types/glob@^7.1.1": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/hoist-non-react-statics@^3.3.1": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a" - integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - -"@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== - -"@types/http-cache-semantics@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz#abe102d06ccda1efdf0ed98c10ccf7f36a785a41" - integrity sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw== - -"@types/inquirer@^6.5.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be" - integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw== - dependencies: - "@types/through" "*" - rxjs "^6.4.0" - -"@types/interpret@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/interpret/-/interpret-1.1.1.tgz#b1bf85b0420e2414b989ce237658ad20dc03719b" - integrity sha512-HZ4d0m2Ebl8DmrOdYZHgYyipj/8Ftq1/ssB/oQR7fqfUrwtTP7IW3BDi2V445nhPBLzZjEkApaPVp83moSCXlA== - dependencies: - "@types/node" "*" - -"@types/is-hotkey@^0.1.1": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.8.tgz#9b792299c8937dd65284d97c71f8077f682ea341" - integrity sha512-4zW6OgrfVWR14IqHt32L5zpsE5IJgAu9uimQmAOFPdKPdv+M5RgXeoB2UCJZSKvVNGzUdLgbKdtCSZ66N2HdTA== - -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - dependencies: - "@types/node" "*" - -"@types/liftoff@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@types/liftoff/-/liftoff-2.5.1.tgz#2eb4c1f86e9d5ee85571e56db0084b26af129ced" - integrity sha512-nB3R6Q9CZcM07JgiTK6ibxqrG1reiHE+UX7em/W1DKwVBxDlfKWOefQjk4jubY5xX+GDxVsWR2KD1SenPby8ow== - dependencies: - "@types/fined" "*" - "@types/interpret" "*" - "@types/node" "*" - -"@types/lodash@^4.14.149": - version "4.14.200" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" - integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== - -"@types/lodash@^4.14.165": - version "4.14.198" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" - integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== - -"@types/minimatch@*": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== - -"@types/node@*": - version "20.6.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.3.tgz#5b763b321cd3b80f6b8dde7a37e1a77ff9358dd9" - integrity sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA== - -"@types/normalize-package-data@^2.4.0": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" - integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/prop-types@*": - version "15.7.6" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" - integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== - -"@types/react-transition-group@^4.4.0": - version "4.4.6" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" - integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@16 || 17 || 18": - version "18.2.22" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" - integrity sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/responselike@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" - -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - -"@types/stylis@^4.0.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.2.tgz#baabb6b3aa6787e90a6bd6cd75cd8fb9a4f256a3" - integrity sha512-Rm17MsTpQQP5Jq4BF7CdrxJsDufoiL/q5IbJZYZmOZAJALyijgF7BzLgobXUqraNcQdqFYLYGeglDp6QzaxPpg== - -"@types/through@*": - version "0.0.31" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.31.tgz#eb410602641807e74a90c5e951f46686e75eed1c" - integrity sha512-LpKpmb7FGevYgXnBXYs6HWnmiFyVG07Pt1cnbgM1IhEacITTiUaBXXvOR3Y50ksaJWGSfhbEvQFivQEFGCC55w== - dependencies: - "@types/node" "*" - -"@types/triple-beam@^1.3.2": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.3.tgz#726ae98a5f6418c8f24f9b0f2a9f81a8664876ae" - integrity sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g== - -"@types/use-sync-external-store@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" - integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== - -"@ucast/core@^1.0.0", "@ucast/core@^1.4.1", "@ucast/core@^1.6.1": - version "1.10.2" - resolved "https://registry.yarnpkg.com/@ucast/core/-/core-1.10.2.tgz#30b6b893479823265368e528b61b042f752f2c92" - integrity sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g== - -"@ucast/js@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@ucast/js/-/js-3.0.3.tgz#6ff618a85bd95f1a8f46658cc663a1f798de327f" - integrity sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ== - dependencies: - "@ucast/core" "^1.0.0" - -"@ucast/mongo2js@^1.3.0": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@ucast/mongo2js/-/mongo2js-1.3.4.tgz#579f9e5eb074cba54640d5c70c71c500580f3af3" - integrity sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA== - dependencies: - "@ucast/core" "^1.6.1" - "@ucast/js" "^3.0.0" - "@ucast/mongo" "^2.4.0" - -"@ucast/mongo@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@ucast/mongo/-/mongo-2.4.3.tgz#92b1dd7c0ab06a907f2ab1422aa3027518ccc05e" - integrity sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA== - dependencies: - "@ucast/core" "^1.4.1" - -"@uiw/codemirror-extensions-basic-setup@4.21.21": - version "4.21.21" - resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.21.21.tgz#243ef309cb53253b14187649a7abc0d996420a20" - integrity sha512-+0i9dPrRSa8Mf0CvyrMvnAhajnqwsP3IMRRlaHDRgsSGL8igc4z7MhvUPn+7cWFAAqWzQRhMdMSWzo6/TEa3EA== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/commands" "^6.0.0" - "@codemirror/language" "^6.0.0" - "@codemirror/lint" "^6.0.0" - "@codemirror/search" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - -"@uiw/react-codemirror@^4.21.20": - version "4.21.21" - resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.21.21.tgz#986b18dbd6dc69aa470fc3d4e47b89b504af6778" - integrity sha512-PaxBMarufMWoR0qc5zuvBSt76rJ9POm9qoOaJbqRmnNL2viaF+d+Paf2blPSlm1JSnqn7hlRjio+40nZJ9TKzw== - dependencies: - "@babel/runtime" "^7.18.6" - "@codemirror/commands" "^6.1.0" - "@codemirror/state" "^6.1.1" - "@codemirror/theme-one-dark" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup" "4.21.21" - codemirror "^6.0.0" - -"@vitejs/plugin-react@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz#e4f56f46fd737c5d386bb1f1ade86ba275fe09bd" - integrity sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ== - dependencies: - "@babel/core" "^7.22.20" - "@babel/plugin-transform-react-jsx-self" "^7.22.5" - "@babel/plugin-transform-react-jsx-source" "^7.22.5" - "@types/babel__core" "^7.20.2" - react-refresh "^0.14.0" - -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== - -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abbrev@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -accepts@^1.3.5: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn-walk@^8.0.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" - integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== - -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.0.4: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== - -acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -addressparser@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" - integrity sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg== - -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - 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" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-html-community@0.0.8, ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -argparse@~1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -aria-hidden@^1.1.1, aria-hidden@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" - integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== - dependencies: - tslib "^2.0.0" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - -asn1.js@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - -async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -aws-sdk@2.1472.0: - version "2.1472.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1472.0.tgz#f9bbdea05b6c0b83e97779f91da01857354f10e9" - integrity sha512-U7kAHRbvTy753IXKV8Oom/AqlqnsbXG+Kw5gRbKi6VcsZ3hR/EpNMzdRXTWO5U415bnLWGo8WAqIz67PIaaKsw== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.5.0" - -axios@0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -axios@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" - integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -b4a@^1.6.4: - version "1.6.4" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" - integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== - -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - -"babel-plugin-styled-components@>= 1.12.0": - version "2.1.4" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" - integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - lodash "^4.17.21" - picomatch "^2.3.1" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcryptjs@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" - integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== - -better-sqlite3@8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.7.0.tgz#bcc341856187b1d110a8a47234fa89c48c8ef538" - integrity sha512-99jZU4le+f3G6aIl6PmmV0cxUIWqKieHxsiF7G34CVFiE+/UabpYqkU0NJIkY/96mQKikHeBjtR27vFfs5JpEw== - dependencies: - bindings "^1.5.0" - prebuild-install "^7.1.1" - -big-integer@^1.6.16: - version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - -big-integer@^1.6.44: - version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bl@^4.0.3, bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bn.js@^4.0.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boxen@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -broadcast-channel@^3.4.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" - integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== - dependencies: - "@babel/runtime" "^7.7.2" - detect-node "^2.1.0" - js-sha3 "0.8.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - rimraf "3.0.2" - unload "2.2.0" - -brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browserslist-to-esbuild@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserslist-to-esbuild/-/browserslist-to-esbuild-1.2.0.tgz#5c5b9ca73106da02e0168007396c4ec4c1e6d643" - integrity sha512-ftrrbI/VHBgEnmnSyhkqvQVMp6jAKybfs0qMIlm7SLBrQTGMsdCIP4q3BoKeLsZTBQllIQtY9kbxgRYV2WU47g== - dependencies: - browserslist "^4.17.3" - -browserslist@^4.14.5, browserslist@^4.17.3, browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -browserslist@^4.22.1: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== - dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" - update-browserslist-db "^1.0.13" - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-writer@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" - integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== - -buffer@4.9.2: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffer@^5.1.0, buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buildmail@3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/buildmail/-/buildmail-3.10.0.tgz#c6826d716e7945bb6f6b1434b53985e029a03159" - integrity sha512-6e5sDN/pl3en5Klqdfyir7LEIBiFr9oqZuvYaEyVwjxpIbBZN+98e0j87Fz2Ukl8ud32rbk9VGOZAnsOZ7pkaA== - dependencies: - addressparser "1.0.1" - libbase64 "0.1.0" - libmime "2.1.0" - libqp "1.1.0" - nodemailer-fetch "1.6.0" - nodemailer-shared "1.1.0" - -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - -byte-size@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" - integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== - -bytes@3.1.2, bytes@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cache-content-type@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" - integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA== - dependencies: - mime-types "^2.1.18" - ylru "^1.2.0" - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" - integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" - -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -caniuse-lite@^1.0.30001517: - version "1.0.30001538" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" - integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== - -caniuse-lite@^1.0.30001541: - version "1.0.30001551" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz#1f2cfa8820bd97c971a57349d7fd8f6e08664a3e" - integrity sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg== - -chalk@4.1.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - 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" - -change-case@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e" - integrity sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw== - dependencies: - camel-case "^3.0.0" - constant-case "^2.0.0" - dot-case "^2.1.0" - header-case "^1.0.0" - is-lower-case "^1.1.0" - is-upper-case "^1.1.0" - lower-case "^1.1.1" - lower-case-first "^1.0.0" - no-case "^2.3.2" - param-case "^2.1.0" - pascal-case "^2.0.0" - path-case "^2.1.0" - sentence-case "^2.1.0" - snake-case "^2.1.0" - swap-case "^1.1.0" - title-case "^2.1.0" - upper-case "^1.1.1" - upper-case-first "^1.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chokidar@3.5.3, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - 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" - optionalDependencies: - fsevents "~2.3.2" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -ci-info@3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -ckeditor5@39.0.2, ckeditor5@^39.0.2: - version "39.0.2" - resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-39.0.2.tgz#29a1429b440ac548f5d73b8870a0a143fceb36d5" - integrity sha512-7wuhb5LYeAZ9PaEZlNNAOnfDLWozt3+tOdc1St7AYR4PO1UW/5crWHvs+6jejxB56Zk6LUaHUS6ECecdapihmA== - dependencies: - "@ckeditor/ckeditor5-clipboard" "39.0.2" - "@ckeditor/ckeditor5-core" "39.0.2" - "@ckeditor/ckeditor5-engine" "39.0.2" - "@ckeditor/ckeditor5-enter" "39.0.2" - "@ckeditor/ckeditor5-paragraph" "39.0.2" - "@ckeditor/ckeditor5-select-all" "39.0.2" - "@ckeditor/ckeditor5-typing" "39.0.2" - "@ckeditor/ckeditor5-ui" "39.0.2" - "@ckeditor/ckeditor5-undo" "39.0.2" - "@ckeditor/ckeditor5-upload" "39.0.2" - "@ckeditor/ckeditor5-utils" "39.0.2" - "@ckeditor/ckeditor5-watchdog" "39.0.2" - "@ckeditor/ckeditor5-widget" "39.0.2" - -ckeditor5@>=35.1.0: - version "40.0.0" - resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-40.0.0.tgz#0415efab6b11da48cc7d58229ce282e3587d2be8" - integrity sha512-pyXptFXODCyICkIaiBfWl+vqNq87CwB4IodaacUDdp+7z7szK13/vMJMWyPCQyZob+lzpakqPpj29HR5Kzy4AQ== - dependencies: - "@ckeditor/ckeditor5-clipboard" "40.0.0" - "@ckeditor/ckeditor5-core" "40.0.0" - "@ckeditor/ckeditor5-engine" "40.0.0" - "@ckeditor/ckeditor5-enter" "40.0.0" - "@ckeditor/ckeditor5-paragraph" "40.0.0" - "@ckeditor/ckeditor5-select-all" "40.0.0" - "@ckeditor/ckeditor5-typing" "40.0.0" - "@ckeditor/ckeditor5-ui" "40.0.0" - "@ckeditor/ckeditor5-undo" "40.0.0" - "@ckeditor/ckeditor5-upload" "40.0.0" - "@ckeditor/ckeditor5-utils" "40.0.0" - "@ckeditor/ckeditor5-watchdog" "40.0.0" - "@ckeditor/ckeditor5-widget" "40.0.0" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@^5.2.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" - integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== - dependencies: - source-map "~0.6.0" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== - dependencies: - restore-cursor "^2.0.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.0.0, cli-spinners@^2.5.0: - version "2.9.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" - integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== - -cli-table3@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a" - integrity sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -co-body@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" - integrity sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ== - dependencies: - inflation "^2.0.0" - qs "^6.4.0" - raw-body "^2.2.0" - type-is "^1.6.14" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -"codemirror5@npm:codemirror@^5.65.11": - version "5.65.15" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.15.tgz#66899278f44a7acde0eb641388cd563fe6dfbe19" - integrity sha512-YC4EHbbwQeubZzxLl5G4nlbLc1T21QTrKGaOal/Pkm9dVDMZXMH7+ieSPEOZCtO9I68i8/oteJKOxzHC2zR+0g== - -codemirror@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29" - integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/commands" "^6.0.0" - "@codemirror/language" "^6.0.0" - "@codemirror/lint" "^6.0.0" - "@codemirror/search" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@2.0.1, color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-parse@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-1.4.2.tgz#78651f5d34df1a57f997643d86f7f87268ad4eb5" - integrity sha512-RI7s49/8yqDj3fECFZjUI1Yi0z/Gq1py43oNJivAIIDSyJiOZLfYCRQEgn8HEVAj++PcRe8AnL2XF0fRJ3BTnA== - dependencies: - color-name "^1.0.0" - -color-string@^1.6.0, color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - -colorette@2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -colorette@^2.0.10: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -colors@~1.2.1: - version "1.2.5" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" - integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@8.3.0, commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -commander@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -common-path-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" - integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -compressible@^2.0.0: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compute-scroll-into-view@^1.0.20: - version "1.0.20" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" - integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== - -compute-scroll-into-view@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz#753f11d972596558d8fe7c6bcbc8497690ab4c87" - integrity sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -condense-newlines@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f" - integrity sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg== - dependencies: - extend-shallow "^2.0.1" - is-whitespace "^0.3.0" - kind-of "^3.0.2" - -config-chain@^1.1.11, config-chain@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - -configstore@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -constant-case@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" - integrity sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ== - dependencies: - snake-case "^2.1.0" - upper-case "^1.1.1" - -content-disposition@~0.5.2: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.5.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4" - integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw== - -cookie@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - -cookies@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" - integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow== - dependencies: - depd "~2.0.0" - keygrip "~1.1.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - -copyfiles@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5" - integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg== - dependencies: - glob "^7.0.5" - minimatch "^3.0.3" - mkdirp "^1.0.4" - noms "0.0.0" - through2 "^2.0.1" - untildify "^4.0.0" - yargs "^16.1.0" - -core-js-pure@^3.23.3, core-js-pure@^3.30.2: - version "3.32.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.32.2.tgz#b7dbdac528625cf87eb0523b532eb61551b9a6d1" - integrity sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ== - -core-util-is@^1.0.2, core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - 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" - -crc@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" - integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== - dependencies: - buffer "^5.1.0" - -crelt@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" - integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== - -cron-parser@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-3.5.0.tgz#b1a9da9514c0310aa7ef99c2f3f1d0f8c235257c" - integrity sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ== - dependencies: - is-nan "^1.3.2" - luxon "^1.26.0" - -cropperjs@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.6.0.tgz#de9f23b7e397a53fd0dc10cc0d6fe31fe1e2019a" - integrity sha512-BzLU/ecrfsbflwxgu+o7sQTrTlo52pVRZkTVrugEK5uyj6n8qKwAHP4s6+DWHqlXLqQ5B9+cM2MKeXiNfAsF6Q== - -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - -cross-spawn@^7.0.1, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-color-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" - integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== - -css-loader@^6.8.1: - version "6.8.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" - integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.21" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.3" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.8" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-to-react-native@^3.0.0, css-to-react-native@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" - integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== - dependencies: - camelize "^1.0.0" - css-color-keywords "^1.0.0" - postcss-value-parser "^4.0.2" - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -csstype@^3.0.2, csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - -date-fns@2.30.0, date-fns@^2.29.3: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" - -debounce@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" - integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== - -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decimal.js@^10.2.1: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -decompress-response@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-7.0.0.tgz#dc42107cc29a258aa8983fddc81c92351810f6fb" - integrity sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA== - dependencies: - mimic-response "^3.1.0" - -deep-equal@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw== - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deepmerge@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" - integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - -define-data-property@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" - integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -define-properties@^1.1.3: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" - integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== - dependencies: - globby "^10.0.1" - graceful-fs "^4.2.2" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.1" - p-map "^3.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -delegates@1.0.0, delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - -detect-indent@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25" - integrity sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g== - -detect-libc@^2.0.0, detect-libc@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" - integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== - -detect-newline@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-4.0.1.tgz#fcefdb5713e1fb8cb2839b8b6ee22e6716ab8f23" - integrity sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog== - -detect-node-es@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" - integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== - -detect-node@^2.0.4, detect-node@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -direction@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/direction/-/direction-1.0.4.tgz#2b86fb686967e987088caf8b89059370d4837442" - integrity sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ== - -dkim-signer@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dkim-signer/-/dkim-signer-0.2.2.tgz#aa81ec071eeed3622781baa922044d7800e5f308" - integrity sha512-24OZ3cCA30UTRz+Plpg+ibfPq3h7tDtsJRg75Bo0pGakZePXcPBddY80bKi1Bi7Jsz7tL5Cw527mhCRDvNFgfg== - dependencies: - libmime "^2.0.3" - -dnd-core@15.1.2: - version "15.1.2" - resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-15.1.2.tgz#0983bce555c4985f58b731ffe1faed31e1ea7f6f" - integrity sha512-EOec1LyJUuGRFg0LDa55rSRAUe97uNVKVkUo8iyvzQlcECYTuPblVQfRWXWj1OyPseFIeebWpNmKFy0h6BcF1A== - dependencies: - "@react-dnd/asap" "4.0.1" - "@react-dnd/invariant" "3.0.1" - redux "^4.1.2" - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -domutils@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -dot-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee" - integrity sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug== - dependencies: - no-case "^2.2.0" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dotenv@14.2.0: - version "14.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.2.0.tgz#7e77fd5dd6cff5942c4496e1acf2d0f37a9e67aa" - integrity sha512-05POuPJyPpO6jqzTNweQFfAyMSD4qa4lvsMOWyTRTdpHKy6nnnN+IYWaXF+lHivhBH/ufDKlR4IWCAN3oPnHuw== - -duplexer@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -editorconfig@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" - integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q== - dependencies: - "@one-ini/wasm" "0.1.1" - commander "^10.0.0" - minimatch "9.0.1" - semver "^7.5.3" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.477: - version "1.4.525" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.525.tgz#614284f33901fbecd3e90176c0d60590cd939700" - integrity sha512-GIZ620hDK4YmIqAWkscG4W6RwY6gOx1y5J6f4JUQwctiJrqH2oxZYU4mXHi35oV32tr630UcepBzSBGJ/WYcZA== - -electron-to-chromium@^1.4.535: - version "1.4.561" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.561.tgz#816f31d9ae01fe58abbf469fca7e125b16befd85" - integrity sha512-eS5t4ulWOBfVHdq9SW2dxEaFarj1lPjvJ8PaYMOjY0DecBaj/t4ARziL2IPpDr4atyWwjLFGQ2vo/VCgQFezVQ== - -elliptic@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emittery@^0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.12.1.tgz#cb9a4a18745816f7a1fa03a8953e7eaededb45f2" - integrity sha512-pYyW59MIZo0HxPFf+Vb3+gacUu0gxVS3TZwB2ClwkEZywgF9f9OJDoVmNLojTn0vKX3tO9LC+pdQEcLP4Oz/bQ== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -encodeurl@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^4.2.0, entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -es-module-lexer@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" - integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== - -esbuild-loader@^2.21.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.21.0.tgz#2698a3e565b0db2bb19a3dd91c2b6c9aad526c80" - integrity sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g== - dependencies: - esbuild "^0.16.17" - joycon "^3.0.1" - json5 "^2.2.0" - loader-utils "^2.0.0" - tapable "^2.2.0" - webpack-sources "^1.4.3" - -esbuild-register@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.5.0.tgz#449613fb29ab94325c722f560f800dd946dc8ea8" - integrity sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A== - dependencies: - debug "^4.3.4" - -esbuild@0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.2.tgz#b1541828a89dfb6f840d38538767c6130dca2aac" - integrity sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg== - optionalDependencies: - "@esbuild/android-arm" "0.19.2" - "@esbuild/android-arm64" "0.19.2" - "@esbuild/android-x64" "0.19.2" - "@esbuild/darwin-arm64" "0.19.2" - "@esbuild/darwin-x64" "0.19.2" - "@esbuild/freebsd-arm64" "0.19.2" - "@esbuild/freebsd-x64" "0.19.2" - "@esbuild/linux-arm" "0.19.2" - "@esbuild/linux-arm64" "0.19.2" - "@esbuild/linux-ia32" "0.19.2" - "@esbuild/linux-loong64" "0.19.2" - "@esbuild/linux-mips64el" "0.19.2" - "@esbuild/linux-ppc64" "0.19.2" - "@esbuild/linux-riscv64" "0.19.2" - "@esbuild/linux-s390x" "0.19.2" - "@esbuild/linux-x64" "0.19.2" - "@esbuild/netbsd-x64" "0.19.2" - "@esbuild/openbsd-x64" "0.19.2" - "@esbuild/sunos-x64" "0.19.2" - "@esbuild/win32-arm64" "0.19.2" - "@esbuild/win32-ia32" "0.19.2" - "@esbuild/win32-x64" "0.19.2" - -esbuild@^0.16.17: - version "0.16.17" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" - integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== - optionalDependencies: - "@esbuild/android-arm" "0.16.17" - "@esbuild/android-arm64" "0.16.17" - "@esbuild/android-x64" "0.16.17" - "@esbuild/darwin-arm64" "0.16.17" - "@esbuild/darwin-x64" "0.16.17" - "@esbuild/freebsd-arm64" "0.16.17" - "@esbuild/freebsd-x64" "0.16.17" - "@esbuild/linux-arm" "0.16.17" - "@esbuild/linux-arm64" "0.16.17" - "@esbuild/linux-ia32" "0.16.17" - "@esbuild/linux-loong64" "0.16.17" - "@esbuild/linux-mips64el" "0.16.17" - "@esbuild/linux-ppc64" "0.16.17" - "@esbuild/linux-riscv64" "0.16.17" - "@esbuild/linux-s390x" "0.16.17" - "@esbuild/linux-x64" "0.16.17" - "@esbuild/netbsd-x64" "0.16.17" - "@esbuild/openbsd-x64" "0.16.17" - "@esbuild/sunos-x64" "0.16.17" - "@esbuild/win32-arm64" "0.16.17" - "@esbuild/win32-ia32" "0.16.17" - "@esbuild/win32-x64" "0.16.17" - -esbuild@^0.18.10: - version "0.18.20" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" - integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== - optionalDependencies: - "@esbuild/android-arm" "0.18.20" - "@esbuild/android-arm64" "0.18.20" - "@esbuild/android-x64" "0.18.20" - "@esbuild/darwin-arm64" "0.18.20" - "@esbuild/darwin-x64" "0.18.20" - "@esbuild/freebsd-arm64" "0.18.20" - "@esbuild/freebsd-x64" "0.18.20" - "@esbuild/linux-arm" "0.18.20" - "@esbuild/linux-arm64" "0.18.20" - "@esbuild/linux-ia32" "0.18.20" - "@esbuild/linux-loong64" "0.18.20" - "@esbuild/linux-mips64el" "0.18.20" - "@esbuild/linux-ppc64" "0.18.20" - "@esbuild/linux-riscv64" "0.18.20" - "@esbuild/linux-s390x" "0.18.20" - "@esbuild/linux-x64" "0.18.20" - "@esbuild/netbsd-x64" "0.18.20" - "@esbuild/openbsd-x64" "0.18.20" - "@esbuild/sunos-x64" "0.18.20" - "@esbuild/win32-arm64" "0.18.20" - "@esbuild/win32-ia32" "0.18.20" - "@esbuild/win32-x64" "0.18.20" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escodegen@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - -eslint-plugin-react-hooks@^4.3.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esm@^3.2.25: - version "3.2.25" - resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" - integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== - -esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -events@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@5.1.1, execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - 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" - -execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-fifo@^1.1.0, fast-fifo@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" - integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== - -fast-glob@^3.0.3: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - 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" - -fast-glob@^3.3.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - 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" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-root@1.1.0, find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -findup-sync@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" - integrity sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g== - dependencies: - detect-file "^1.0.0" - is-glob "^3.1.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -fined@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -follow-redirects@^1.14.9, follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== - -follow-redirects@^1.15.2: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - -fork-ts-checker-webpack-plugin@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz#dae45dfe7298aa5d553e2580096ced79b6179504" - integrity sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg== - dependencies: - "@babel/code-frame" "^7.16.7" - chalk "^4.1.2" - chokidar "^3.5.3" - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - fs-extra "^10.0.0" - memfs "^3.4.1" - minimatch "^3.0.4" - node-abort-controller "^3.0.1" - schema-utils "^3.1.1" - semver "^7.3.5" - tapable "^2.2.1" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -formidable@^1.1.1: - version "1.2.6" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" - integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== - -formik@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.0.tgz#8243e42a89e1c9fbe9aefbd48bc8d1f10ae2950d" - integrity sha512-QZiWztt9fD84EYcF7Bmr431ZhIm1xUVgBACbTuJ6azPrUpVp7o6q+t9HJaIQsFZrMfcBPNBotYtDgyDpzQ3z0Q== - dependencies: - deepmerge "^2.1.1" - hoist-non-react-statics "^3.3.0" - lodash "^4.17.21" - lodash-es "^4.17.21" - react-fast-compare "^2.0.1" - tiny-warning "^1.0.2" - tslib "^1.10.0" - -fractional-indexing@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628" - integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - -fresh@~0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -from2@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-extra@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-jetpack@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/fs-jetpack/-/fs-jetpack-4.3.1.tgz#cdfd4b64e6bfdec7c7dc55c76b39efaa7853bb20" - integrity sha512-dbeOK84F6BiQzk2yqqCVwCPWTxAvVGJ3fMQc6E2wuEohS28mR6yHngbrKuVCK1KHRx/ccByDylqu4H5PCP2urQ== - dependencies: - minimatch "^3.0.2" - rimraf "^2.6.3" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747" - integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-it@^8.0.9: - version "8.4.4" - resolved "https://registry.yarnpkg.com/get-it/-/get-it-8.4.4.tgz#8c1c4b16f6f2da4120c00fffa66c5afe2d454e23" - integrity sha512-Pu3pnJfnYuLEhwJgMlFqk19ugvtazzTxh7rg8wATaBL4c5Fy4ahM5B+bGdluiNSNYYK89F5vSa+N3sTa/qqtlg== - dependencies: - debug "^4.3.4" - decompress-response "^7.0.0" - follow-redirects "^1.15.2" - into-stream "^6.0.0" - is-plain-object "^5.0.0" - is-retry-allowed "^2.2.0" - is-stream "^2.0.1" - parse-headers "^2.0.5" - progress-stream "^2.0.0" - tunnel-agent "^0.6.0" - -get-latest-version@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-latest-version/-/get-latest-version-5.1.0.tgz#928f7fda59a9a34d7c7cf3664a2006dfd9af6aa7" - integrity sha512-Q6IBWr/zzw57zIkJmNhI23eRTw3nZ4BWWK034meLwOYU9L3J3IpXiyM73u2pYUwN6U7ahkerCwg2T0jlxiLwsw== - dependencies: - get-it "^8.0.9" - registry-auth-token "^5.0.2" - registry-url "^5.1.0" - semver "^7.3.8" - -get-nonce@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" - integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stdin@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" - integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0, get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - -getopts@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" - integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== - -git-hooks-list@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-3.1.0.tgz#386dc531dcc17474cf094743ff30987a3d3e70fc" - integrity sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA== - -git-up@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" - integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== - dependencies: - is-ssh "^1.4.0" - parse-url "^8.1.0" - -git-url-parse@13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" - integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== - dependencies: - git-up "^7.0.0" - -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@7.2.3, glob@^7.0.5, glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - 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" - -glob@^8.0.3, glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - -globby@^13.1.2: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -got@^11.8.2: - version "11.8.6" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -graceful-fs@4.2.10: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -grant-koa@5.4.8: - version "5.4.8" - resolved "https://registry.yarnpkg.com/grant-koa/-/grant-koa-5.4.8.tgz#2fc49ad91007588fff58559cffa44dca9f06835d" - integrity sha512-Kw8np9AL3Z3mZuvoSUklHJpTe3xx7iLBDauRyIwwbDLRr/5Ll6APmOFHixXj+Vw+LGEnreTxO35CyhAf9oBUMA== - dependencies: - grant "^5.4.8" - -grant@^5.4.8: - version "5.4.21" - resolved "https://registry.yarnpkg.com/grant/-/grant-5.4.21.tgz#3306942f4a19e40d008e247d071104b19173c0c6" - integrity sha512-QaoZudI9Gmh2W415gd71Iul6gpVH9sG1SkjfnGHtqYZopQDQ5PUVxRol5zFCrwGi9S0EbExbelHlZScgdChg2w== - dependencies: - qs "^6.10.2" - request-compose "^2.1.4" - request-oauth "^1.0.1" - optionalDependencies: - cookie "^0.4.1" - cookie-signature "^1.1.0" - jwk-to-pem "^2.0.5" - jws "^4.0.0" - -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - -handlebars@^4.4.3: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.2" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -header-case@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d" - integrity sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ== - dependencies: - no-case "^2.2.0" - upper-case "^1.1.3" - -helmet@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-6.2.0.tgz#c29d62014be4c70b8ef092c9c5e54c8c26b8e16e" - integrity sha512-DWlwuXLLqbrIOltR6tFQXShj/+7Cyp0gLi6uAb8qMdFh/YBBFbKSgQ6nbXmScYd8emMctuthmgIa7tUfo9Rtyg== - -highlight.js@^10.4.1: - version "10.7.3" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== - -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== - dependencies: - whatwg-encoding "^1.0.5" - -html-entities@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - -html-escaper@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-minifier-terser@^6.0.2: - version "6.1.0" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== - dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" - -html-to-text@9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.3.tgz#331368f32fcb270c59dbd3a7fdb32813d2a490bc" - integrity sha512-hxDF1kVCF2uw4VUJ3vr2doc91pXf2D5ngKcNviSitNkhP9OMOaJkDrFIFL6RMvko7NisWTEiqGpQ9LAxcVok1w== - dependencies: - "@selderee/plugin-htmlparser2" "^0.10.0" - deepmerge "^4.2.2" - dom-serializer "^2.0.0" - htmlparser2 "^8.0.1" - selderee "^0.10.0" - -html-webpack-plugin@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== - dependencies: - "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -htmlparser2@^8.0.0, htmlparser2@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" - integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.0.1" - entities "^4.4.0" - -http-assert@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f" - integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w== - dependencies: - deep-equal "~1.0.1" - http-errors "~1.8.0" - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-errors@1.8.1, http-errors@^1.6.3, http-errors@^1.7.3, http-errors@^1.8.0, http-errors@~1.8.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - 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" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -iconv-lite@0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" - integrity sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ== - -iconv-lite@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - integrity sha512-RGR+c9Lm+tLsvU57FTJJtdbv2hQw42Yl2n26tVIBaYmZzLN+EGfroUugN/z9nJf9kOXd49hBmpoGr4FEm+A4pw== - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ieee754@1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -ieee754@^1.1.13, ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.1.1: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -ignore@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" - integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== - -immer@9.0.19: - version "9.0.19" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.19.tgz#67fb97310555690b5f9cd8380d38fc0aabb6b38b" - integrity sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ== - -immer@^9.0.21, immer@^9.0.6: - version "9.0.21" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" - integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflation@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f" - integrity sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -ini@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" - integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== - -ini@^1.3.4, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inquirer@8.2.5: - version "8.2.5" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" - integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.5.5" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - wrap-ansi "^7.0.0" - -inquirer@^7.1.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -interpret@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - -intl-messageformat@10.3.4: - version "10.3.4" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.3.4.tgz#20f064c28b46fa6d352a4c4ba5e9bfc597af3eba" - integrity sha512-/FxUIrlbPtuykSNX85CB5sp2FjLVeTmdD7TfRkVFPft2n4FgcSlAcilFytYiFAEmPHc+0PvpLCIPXeaGFzIvOg== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/fast-memoize" "2.0.1" - "@formatjs/icu-messageformat-parser" "2.3.1" - tslib "^2.4.0" - -into-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" - integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== - dependencies: - from2 "^2.3.0" - p-is-promise "^3.0.0" - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.3: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-class-hotfix@~0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz#a527d31fb23279281dde5f385c77b5de70a72435" - integrity sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ== - -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-docker@2.2.1, is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-hotkey@^0.1.6: - version "0.1.8" - resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.1.8.tgz#6b1f4b2d0e5639934e20c05ed24d623a21d36d25" - integrity sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ== - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-lower-case@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" - integrity sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA== - dependencies: - lower-case "^1.1.0" - -is-nan@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" - integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-inside@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" - integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - -is-retry-allowed@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d" - integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== - -is-ssh@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" - integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== - dependencies: - protocols "^2.0.1" - -is-stream@^2.0.0, is-stream@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-type-of@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/is-type-of/-/is-type-of-1.4.0.tgz#3ed175a0eee888b1da4983332e7714feb8a8fb2b" - integrity sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ== - dependencies: - core-util-is "^1.0.2" - is-class-hotfix "~0.0.6" - isstream "~0.1.2" - -is-typed-array@^1.1.3: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-upper-case@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" - integrity sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw== - dependencies: - upper-case "^1.1.0" - -is-whitespace@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" - integrity sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg== - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isbinaryfile@^4.0.2: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -isstream@^0.1.2, isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jmespath@0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" - integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== - -joycon@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" - integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== - -js-beautify@^1.6.12: - version "1.14.9" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.9.tgz#a5db728bc5a0d84d3b1a597c376b29bd4d39c8e5" - integrity sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg== - dependencies: - config-chain "^1.1.13" - editorconfig "^1.0.3" - glob "^8.1.0" - nopt "^6.0.0" - -js-cookie@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" - integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== - -js-sha3@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsdom@^16.2.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json5@^2.1.2, json5@^2.2.0, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonwebtoken@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== - dependencies: - jws "^3.2.2" - lodash "^4.17.21" - ms "^2.1.1" - semver "^7.3.8" - -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jwk-to-pem@2.0.5, jwk-to-pem@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz#151310bcfbcf731adc5ad9f379cbc8b395742906" - integrity sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A== - dependencies: - asn1.js "^5.3.0" - elliptic "^6.5.4" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -keygrip@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" - integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== - dependencies: - tsscmp "1.0.6" - -keyv@^4.0.0: - version "4.5.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" - integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== - dependencies: - json-buffer "3.0.1" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -knex@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.0.tgz#5c99245e9c7e6a9d1a8b7fcf48b457ccb471dec3" - integrity sha512-h6Ru3PJmZjCDUEqLgwQ/RJUu06Bz7MTzY6sD90udLIa9qwtC7Rnicr7TBiWSaswZmDqk4EZ8xysdg1fkvhYM6w== - dependencies: - colorette "2.0.19" - commander "^10.0.0" - debug "4.3.4" - escalade "^3.1.1" - esm "^3.2.25" - get-package-type "^0.1.0" - getopts "2.3.0" - interpret "^2.2.0" - lodash "^4.17.21" - pg-connection-string "2.6.1" - rechoir "^0.8.0" - resolve-from "^5.0.0" - tarn "^3.0.2" - tildify "2.0.0" - -koa-body@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f" - integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA== - dependencies: - "@types/formidable" "^1.0.31" - co-body "^5.1.1" - formidable "^1.1.1" - -koa-compose@4.1.0, koa-compose@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" - integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== - -koa-compress@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/koa-compress/-/koa-compress-5.1.0.tgz#7b9fe24f4c1b28d9cae90864597da472c2fcf701" - integrity sha512-G3Ppo9jrUwlchp6qdoRgQNMiGZtM0TAHkxRZQ7EoVvIG8E47J4nAsMJxXHAUQ+0oc7t0MDxSdONWTFcbzX7/Bg== - dependencies: - bytes "^3.0.0" - compressible "^2.0.0" - http-errors "^1.8.0" - koa-is-json "^1.0.0" - statuses "^2.0.1" - -koa-convert@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5" - integrity sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA== - dependencies: - co "^4.6.0" - koa-compose "^4.1.0" - -koa-favicon@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/koa-favicon/-/koa-favicon-2.1.0.tgz#c430cc594614fb494adcb5ee1196a2f7f53ea442" - integrity sha512-LvukcooYjxKtnZq0RXdBup+JDhaHwLgnLlDHB/xvjwQEjbc4rbp/0WkmOzpOvaHujc+fIwPear0dpKX1V+dHVg== - dependencies: - mz "^2.7.0" - -koa-helmet@7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/koa-helmet/-/koa-helmet-7.0.2.tgz#2077e60cc69fa550802931ccdb85f948aa6bd054" - integrity sha512-AvzS6VuEfFgbAm0mTUnkk/BpMarMcs5A56g+f0sfrJ6m63wII48d2GDrnUQGp0Nj+RR950vNtgqXm9UJSe7GOg== - dependencies: - helmet "^6.0.1" - -koa-ip@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/koa-ip/-/koa-ip-2.1.3.tgz#b7318bb30fd1e06d03a96beb704ee72cc6ecade0" - integrity sha512-QLVBByImwDq9enZXVOD3Astk876B7N0IYta7Kik4iyNB462rVzBB1/LD0Ek1F+v9nGUTHBFyhh8043EIlskK9Q== - dependencies: - debug "4.3.4" - lodash.isplainobject "4.0.6" - request-ip "3.3.0" - -koa-is-json@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" - integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw== - -koa-passport@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-5.0.0.tgz#66c8e91b06358969ab6129d90368fa07a06fafc0" - integrity sha512-eNGg3TGgZ4ydm9DYCOqaa0ySSA/44BS6X+v4CKjP/nHOoXlADRonHsZvS3QWok6EV0ZL0V7FhfWxRYfD2B5kTQ== - dependencies: - passport "^0.6.0" - -koa-range@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/koa-range/-/koa-range-0.3.0.tgz#3588e3496473a839a1bd264d2a42b1d85bd7feac" - integrity sha512-Ich3pCz6RhtbajYXRWjIl6O5wtrLs6kE3nkXc9XmaWe+MysJyZO7K4L3oce1Jpg/iMgCbj+5UCiMm/rqVtcDIg== - dependencies: - stream-slice "^0.1.2" - -koa-send@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79" - integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ== - dependencies: - debug "^4.1.1" - http-errors "^1.7.3" - resolve-path "^1.4.0" - -koa-session@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/koa-session/-/koa-session-6.4.0.tgz#f17c6f1844b37114192aa23a0ccf4f58c3042e96" - integrity sha512-h/dxmSOvNEXpHQPRs4TV03TZVFyZIjmYQiTAW5JBFTYBOZ0VdpZ8QEE6Dud75g8z9JNGXi3m++VqRmqToB+c2A== - dependencies: - crc "^3.8.0" - debug "^4.3.3" - is-type-of "^1.2.1" - uuid "^8.3.2" - -koa-static@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943" - integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ== - dependencies: - debug "^3.1.0" - koa-send "^5.0.0" - -koa2-ratelimit@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/koa2-ratelimit/-/koa2-ratelimit-1.1.3.tgz#9f839c4f5533151aa4d5b8d11381a9a07854f0ff" - integrity sha512-gdrIw6m/D7pmScScL4dz50qLbRR3UGqvO1Vuy2dc7hVIuFAl1OVTnu6WFyEJ5GbfyLZFaCMWzRw6t4krvzvUTg== - -koa@2.13.4: - version "2.13.4" - resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e" - integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g== - dependencies: - accepts "^1.3.5" - cache-content-type "^1.0.0" - content-disposition "~0.5.2" - content-type "^1.0.4" - cookies "~0.8.0" - debug "^4.3.2" - delegates "^1.0.0" - depd "^2.0.0" - destroy "^1.0.4" - encodeurl "^1.0.2" - escape-html "^1.0.3" - fresh "~0.5.2" - http-assert "^1.3.0" - http-errors "^1.6.3" - is-generator-function "^1.0.7" - koa-compose "^4.1.0" - koa-convert "^2.0.0" - on-finished "^2.3.0" - only "~0.0.2" - parseurl "^1.3.2" - statuses "^1.5.0" - type-is "^1.6.16" - vary "^1.1.2" - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -leac@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" - integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg== - -libbase64@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-0.1.0.tgz#62351a839563ac5ff5bd26f12f60e9830bb751e6" - integrity sha512-B91jifmFw1DKEqEWstSpg1PbtUbBzR4yQAPT86kCQXBtud1AJVA+Z6RSklSrqmKe4q2eiEufgnhqJKPgozzfIQ== - -libmime@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/libmime/-/libmime-2.1.0.tgz#51bc76de2283161eb9051c4bc80aed713e4fd1cd" - integrity sha512-4be2R6/jOasyPTw0BkpIZBVk2cElqjdIdS0PRPhbOCV4wWuL/ZcYYpN1BCTVB+6eIQ0uuAwp5hQTHFrM5Joa8w== - dependencies: - iconv-lite "0.4.13" - libbase64 "0.1.0" - libqp "1.1.0" - -libmime@^2.0.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/libmime/-/libmime-2.1.3.tgz#25017ca5ab5a1e98aadbe2725017cf1d48a42a0c" - integrity sha512-ABr2f4O+K99sypmkF/yPz2aXxUFHEZzv+iUkxItCeKZWHHXdQPpDXd6rV1kBBwL4PserzLU09EIzJ2lxC9hPfQ== - dependencies: - iconv-lite "0.4.15" - libbase64 "0.1.0" - libqp "1.1.0" - -libqp@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8" - integrity sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA== - -liftoff@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" - integrity sha512-01zfGFqfORP1CGmZZP2Zn51zsqz4RltDi0RDOhbGoLYdUT5Lw+I2gX6QdwXhPITF6hPOHEOp+At6/L24hIg9WQ== - dependencies: - extend "^3.0.0" - findup-sync "^2.0.0" - fined "^1.0.1" - flagged-respawn "^1.0.0" - is-plain-object "^2.0.4" - object.map "^1.0.0" - rechoir "^0.6.2" - resolve "^1.1.7" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -loader-utils@^2.0.0, loader-utils@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash-es@4.17.21, lodash-es@^4.17.15, lodash-es@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - -lodash.deburr@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" - integrity sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ== - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - -lodash.isplainobject@4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== - dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -long-timeout@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" - integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lower-case-first@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" - integrity sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA== - dependencies: - lower-case "^1.1.2" - -lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lru_map@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" - integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== - -luxon@^1.26.0: - version "1.28.1" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0" - integrity sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw== - -mailcomposer@3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-3.12.0.tgz#9c5e1188aa8e1c62ec8b86bd43468102b639e8f9" - integrity sha512-zBeDoKUTNI8IAsazoMQFt3eVSVRtDtgrvBjBVdBjxDEX+5KLlKtEFCrBXnxPhs8aTYufUS1SmbFnGpjHS53deg== - dependencies: - buildmail "3.10.0" - libmime "2.1.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - -map-cache@^0.2.0, map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - -markdown-it-abbr@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz#d66b5364521cbb3dd8aa59dadfba2fb6865c8fd8" - integrity sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg== - -markdown-it-container@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b" - integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw== - -markdown-it-deflist@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz#50d7a56b9544cd81252f7623bd785e28a8dcef5c" - integrity sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg== - -markdown-it-emoji@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz#cd42421c2fda1537d9cc12b9923f5c8aeb9029c8" - integrity sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ== - -markdown-it-footnote@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz#e0e4c0d67390a4c5f0c75f73be605c7c190ca4d8" - integrity sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w== - -markdown-it-ins@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz#c09356b917cf1dbf73add0b275d67ab8c73d4b4d" - integrity sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw== - -markdown-it-mark@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz#51257db58787d78aaf46dc13418d99a9f3f0ebd3" - integrity sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A== - -markdown-it-sub@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8" - integrity sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q== - -markdown-it-sup@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3" - integrity sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ== - -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -marked@4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" - integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== - -match-sorter@^6.0.2: - version "6.3.1" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" - integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== - dependencies: - "@babel/runtime" "^7.12.5" - remove-accents "0.4.2" - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.1, memfs@^3.4.12: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -"memoize-one@>=3.1.1 <6": - version "5.2.1" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" - integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== - -memoize-one@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" - integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^3.0.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@2.1.35, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@^2.1.28, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -mini-css-extract-plugin@2.7.6: - version "2.7.6" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d" - integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw== - dependencies: - schema-utils "^4.0.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass@^3.0.0: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^4.0.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp@^0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -mrmime@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" - integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multistream@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/multistream/-/multistream-4.1.0.tgz#7bf00dfd119556fbc153cff3de4c6d477909f5a8" - integrity sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw== - dependencies: - once "^1.4.0" - readable-stream "^3.6.0" - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== - dependencies: - big-integer "^1.6.16" - -nanoclone@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" - integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== - -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -no-case@^2.2.0, no-case@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-abi@^3.3.0: - version "3.47.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8" - integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A== - dependencies: - semver "^7.3.5" - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-addon-api@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" - integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== - -node-fetch@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-machine-id@1.1.12, node-machine-id@^1.1.10: - version "1.1.12" - resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" - integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== - -node-plop@0.26.3, node-plop@^0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/node-plop/-/node-plop-0.26.3.tgz#d6fa7e71393c8b940513ba8c4868f8aaa6dea9df" - integrity sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q== - dependencies: - "@babel/runtime-corejs3" "^7.9.2" - "@types/inquirer" "^6.5.0" - change-case "^3.1.0" - del "^5.1.0" - globby "^10.0.1" - handlebars "^4.4.3" - inquirer "^7.1.0" - isbinaryfile "^4.0.2" - lodash.get "^4.4.2" - mkdirp "^0.5.1" - resolve "^1.12.0" - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -node-schedule@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-2.1.0.tgz#068ae38d7351c330616f7fe7cdb05036f977cbaf" - integrity sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ== - dependencies: - cron-parser "^3.5.0" - long-timeout "0.1.1" - sorted-array-functions "^1.3.0" - -nodemailer-fetch@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz#79c4908a1c0f5f375b73fe888da9828f6dc963a4" - integrity sha512-P7S5CEVGAmDrrpn351aXOLYs1R/7fD5NamfMCHyi6WIkbjS2eeZUB/TkuvpOQr0bvRZicVqo59+8wbhR3yrJbQ== - -nodemailer-shared@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz#cf5994e2fd268d00f5cf0fa767a08169edb07ec0" - integrity sha512-68xW5LSyPWv8R0GLm6veAvm7E+XFXkVgvE3FW0FGxNMMZqMkPFeGDVALfR1DPdSfcoO36PnW7q5AAOgFImEZGg== - dependencies: - nodemailer-fetch "1.6.0" - -noms@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" - integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow== - dependencies: - inherits "^2.0.1" - readable-stream "~1.0.31" - -nopt@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" - integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== - dependencies: - abbrev "^1.0.0" - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npm-run-path@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.2.0.tgz#224cdd22c755560253dd71b83a1ef2f758b2e955" - integrity sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg== - dependencies: - path-key "^4.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -nwsapi@^2.2.0: - version "2.2.7" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" - integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== - -oauth-sign@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.map@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0, object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - -on-finished@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== - dependencies: - mimic-fn "^1.0.0" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -only@~0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" - integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== - -open@8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - -opener@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -ora@5.4.1, ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -ora@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -outdent@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.8.0.tgz#2ebc3e77bf49912543f1008100ff8e7f44428eb0" - integrity sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A== - -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== - -p-is-promise@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" - integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-7.0.0.tgz#1355416e50a5c1b8f1a6f471197a3650d21186bf" - integrity sha512-CHJqc94AA8YfSLHGQT3DbvSIuE12NLFekpM4n7LRrAd3dOJtA911+4xe9q6nC3/jcKraq7nNS9VxgtT0KC+diA== - dependencies: - got "^11.8.2" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^7.3.5" - -packet-reader@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" - integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== - -param-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== - dependencies: - no-case "^2.2.0" - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-headers@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" - integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - 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" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - -parse-path@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" - integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== - dependencies: - protocols "^2.0.0" - -parse-srcset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" - integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== - -parse-url@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" - integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== - dependencies: - parse-path "^7.0.0" - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parseley@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.11.0.tgz#1ff817c829a02fcc214c9cc0d96b126d772ee814" - integrity sha512-VfcwXlBWgTF+unPcr7yu3HSSA6QUdDaDnrHcytVfj5Z8azAyKBDrYnSIfeSxlrEayndNcLmrXzg+Vxbo6DWRXQ== - dependencies: - leac "^0.6.0" - peberminta "^0.8.0" - -parseurl@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e" - integrity sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ== - dependencies: - camel-case "^3.0.0" - upper-case-first "^1.1.0" - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - -passport-local@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" - integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow== - dependencies: - passport-strategy "1.x.x" - -passport-strategy@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" - integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== - -passport@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" - integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== - dependencies: - passport-strategy "1.x.x" - pause "0.0.1" - utils-merge "^1.0.1" - -path-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" - integrity sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q== - dependencies: - no-case "^2.2.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@1.0.1, path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-to-regexp@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" - integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pause@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" - integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== - -peberminta@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.8.0.tgz#acf7b105f3d13c8ac28cad81f2f5fe4698507590" - integrity sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw== - -pg-cloudflare@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" - integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== - -pg-connection-string@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" - integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== - -pg-connection-string@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" - integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" - integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== - -pg-protocol@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" - integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== - -pg-types@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@8.11.3: - version "8.11.3" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" - integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.6.2" - pg-pool "^3.6.1" - pg-protocol "^1.6.0" - pg-types "^2.1.0" - pgpass "1.x" - optionalDependencies: - pg-cloudflare "^1.1.1" - -pgpass@1.x: - version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-up@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - -plop@2.7.6: - version "2.7.6" - resolved "https://registry.yarnpkg.com/plop/-/plop-2.7.6.tgz#1fa5360cd5b04e9932ce677bb6bd44750d97ae67" - integrity sha512-IgnYAsC3Ni7t1cDU7wH2151CD22YhMxH8PFh+iPzCf+WuGEWXslJ5t1Tpr0N/gjL23CAV/HbLAWug2IPM2YrHg== - dependencies: - "@types/liftoff" "^2.5.1" - chalk "^1.1.3" - interpret "^1.2.0" - liftoff "^2.5.0" - minimist "^1.2.5" - node-plop "^0.26.3" - ora "^3.4.0" - v8flags "^2.0.10" - -pluralize@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" - integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== - -pony-cause@^2.1.2: - version "2.1.10" - resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.10.tgz#828457ad6f13be401a075dbf14107a9057945174" - integrity sha512-3IKLNXclQgkU++2fSi93sQ6BznFuxSLB11HdvZQ6JW/spahf/P1pAHBQEahr20rs0htZW0UDkM1HmA+nZkXKsw== - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" - integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.13" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.27: - version "8.4.30" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7" - integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.31: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prebuild-install@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - -prettier-plugin-packagejson@2.4.5: - version "2.4.5" - resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.5.tgz#20cc396e5654b5736657bd2dfb7ac859afc618cc" - integrity sha512-glG71jE1gO3y5+JNAhC8X+4yrlN28rub6Aj461SKbaPie9RgMiHKcInH2Moi2VGOfkTXaEHBhg4uVMBqa+kBUA== - dependencies: - sort-package-json "2.5.1" - synckit "0.8.5" - -prettier@2.8.4: - version "2.8.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" - integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== - -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== - dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" - -pretty@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5" - integrity sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w== - dependencies: - condense-newlines "^0.2.1" - extend-shallow "^2.0.1" - js-beautify "^1.6.12" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -progress-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-2.0.0.tgz#fac63a0b3d11deacbb0969abcc93b214bce19ed5" - integrity sha512-xJwOWR46jcXUq6EH9yYyqp+I52skPySOeHfkxOZ2IY1AiBi/sFJhbhAKHoV3OTw/omQ45KTio9215dRJ2Yxd3Q== - dependencies: - speedometer "~1.0.0" - through2 "~2.0.3" - -prompts@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -property-expr@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" - integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== - -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - -protocols@^2.0.0, protocols@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" - integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -purest@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/purest/-/purest-4.0.2.tgz#6d60403f00731bbe3c508955c96d56e8c0f30098" - integrity sha512-Uq6kdia8zGVHOb/0zAOb7FvKFMKeyeTZTLEwpO0JR3cIFEkpH6asv3ls9M9URDjHiYIdgAPmht5ecSbvPacfyg== - dependencies: - "@simov/deep-extend" "^1.0.0" - qs "^6.10.3" - request-compose "^2.1.4" - request-multipart "^1.0.0" - request-oauth "^1.0.1" - -qs@6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" - integrity sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ== - dependencies: - side-channel "^1.0.4" - -qs@^6.10.2, qs@^6.10.3, qs@^6.4.0, qs@^6.9.6: - version "6.11.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== - dependencies: - side-channel "^1.0.4" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -queue-tick@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" - integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@^2.2.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@1.2.8, rc@^1.2.7, rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-dnd-html5-backend@15.1.3: - version "15.1.3" - resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-15.1.3.tgz#57b4f47e0f23923e7c243d2d0eefe490069115a9" - integrity sha512-HH/8nOEmrrcRGHMqJR91FOwhnLlx5SRLXmsQwZT3IPcBjx88WT+0pWC5A4tDOYDdoooh9k+KMPvWfxooR5TcOA== - dependencies: - dnd-core "15.1.2" - -react-dnd@15.1.2: - version "15.1.2" - resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-15.1.2.tgz#211b30fd842326209c63f26f1bdf1bc52eef4f64" - integrity sha512-EaSbMD9iFJDY/o48T3c8wn3uWU+2uxfFojhesZN3LhigJoAIvH2iOjxofSA9KbqhAKP6V9P853G6XG8JngKVtA== - dependencies: - "@react-dnd/invariant" "3.0.1" - "@react-dnd/shallowequal" "3.0.1" - dnd-core "15.1.2" - fast-deep-equal "^3.1.3" - hoist-non-react-statics "^3.3.2" - -react-dom@18.2.0, react-dom@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.0" - -react-error-boundary@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" - integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== - dependencies: - "@babel/runtime" "^7.12.5" - -react-fast-compare@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" - integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== - -react-fast-compare@^3.1.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" - integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== - -react-helmet@6.1.0, react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== - dependencies: - object-assign "^4.1.1" - prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" - -react-intl@6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.4.1.tgz#01e4bd5497cb93d87146e966d8eda25851d4d9b6" - integrity sha512-/aT5595AEMZ+Pjmt8W2R5/ZkYJmyyd6jTzHzqhJ1LnfeG36+N5huBtykxYhHqLc1BrIRQ1fTX1orYC0Ej5ojtg== - dependencies: - "@formatjs/ecma402-abstract" "1.14.3" - "@formatjs/icu-messageformat-parser" "2.3.1" - "@formatjs/intl" "2.7.1" - "@formatjs/intl-displaynames" "6.3.1" - "@formatjs/intl-listformat" "7.2.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/react" "16 || 17 || 18" - hoist-non-react-statics "^3.3.2" - intl-messageformat "10.3.4" - tslib "^2.4.0" - -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-is@^18.0.0, react-is@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -react-query@3.39.3: - version "3.39.3" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" - integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== - dependencies: - "@babel/runtime" "^7.5.5" - broadcast-channel "^3.4.1" - match-sorter "^6.0.2" - -react-redux@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a" - integrity sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA== - dependencies: - "@babel/runtime" "^7.12.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/use-sync-external-store" "^0.0.3" - hoist-non-react-statics "^3.3.2" - react-is "^18.0.0" - use-sync-external-store "^1.0.0" - -react-refresh@0.14.0, react-refresh@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" - integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== - -react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" - integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A== - dependencies: - react-style-singleton "^2.2.1" - tslib "^2.0.0" - -react-remove-scroll@2.5.5: - version "2.5.5" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" - 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" - -react-remove-scroll@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb" - integrity sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA== - dependencies: - react-remove-scroll-bar "^2.3.4" - react-style-singleton "^2.2.1" - tslib "^2.1.0" - use-callback-ref "^1.3.0" - use-sidecar "^1.1.2" - -react-router-dom@5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" - integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.3.4" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" - integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-select@5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.0.tgz#82921b38f1fcf1471a0b62304da01f2896cd8ce6" - integrity sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ== - 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" - -react-side-effect@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" - integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== - -react-style-singleton@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" - integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== - dependencies: - get-nonce "^1.0.0" - invariant "^2.2.4" - tslib "^2.0.0" - -react-transition-group@^4.3.0: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react-window@1.8.8: - version "1.8.8" - resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.8.tgz#1b52919f009ddf91970cbdb2050a6c7be44df243" - integrity sha512-D4IiBeRtGXziZ1n0XklnFGu7h9gU684zepqyKzgPNzrsrk7xOCxni+TCckjg2Nr/DiaEEGVVmnhYSlT2rB47dQ== - dependencies: - "@babel/runtime" "^7.0.0" - memoize-one ">=3.1.1 <6" - -react@18.2.0, react@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== - dependencies: - loose-envify "^1.1.0" - -read-pkg-up@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -readable-stream@^2.0.0, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~1.0.31: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -redux-thunk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" - integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== - -redux@^4.1.2, redux@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" - integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== - dependencies: - "@babel/runtime" "^7.9.2" - -regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -registry-auth-token@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" - integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== - dependencies: - rc "1.2.8" - -registry-auth-token@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.0.2.tgz#8b026cc507c8552ebbe06724136267e63302f756" - integrity sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ== - dependencies: - "@pnpm/npm-conf" "^2.1.0" - -registry-url@^5.0.0, registry-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== - -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -request-compose@^2.1.4: - version "2.1.6" - resolved "https://registry.yarnpkg.com/request-compose/-/request-compose-2.1.6.tgz#f498d6afd4ce983066432446d4b1e3d3d51fbd0f" - integrity sha512-S07L+2VbJB32WddD/o/PnYGKym63zLVbymygVWXvt8L79VAngcjAxhHaGuFOICLxEV90EasEPzqPKKHPspXP8w== - -request-ip@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-3.3.0.tgz#863451e8fec03847d44f223e30a5d63e369fa611" - integrity sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA== - -request-multipart@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/request-multipart/-/request-multipart-1.0.0.tgz#26fe57634e379a5686eb499788ecd8b4fd51deaf" - integrity sha512-dazx88T19dIKFNc0XdlZV8H46D2RmNFdR4mipcbrFOaN70PSSSMM3urVY+eVbrpraf/fHXccxFhLvG1wkSUtKQ== - dependencies: - bl "^4.0.3" - isstream "^0.1.2" - mime-types "^2.1.28" - multistream "^4.0.1" - uuid "^8.3.2" - -request-oauth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/request-oauth/-/request-oauth-1.0.1.tgz#dedb0c4a37234d9e93f377ddb0aaab425f31239e" - integrity sha512-85THTg1RgOYtqQw42JON6AqvHLptlj1biw265Tsq4fD4cPdUvhDB2Qh9NTv17yCD322ROuO9aOmpc4GyayGVBA== - dependencies: - oauth-sign "^0.9.0" - qs "^6.9.6" - uuid "^8.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -reselect@^4.1.8: - version "4.1.8" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" - integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== - -resend@0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/resend/-/resend-0.11.1.tgz#e494c2c5b110f7169117517896a8d8757226fb71" - integrity sha512-HntGZRVZ6dW5ngAC/fv+StNhMHuJjBT7ZBsmw0iZWN4t4cN5JS8w2Nrt7NtgnDbOfWSdxFm+maCS+f4OGLQyZA== - dependencies: - "@react-email/render" "0.0.5" - axios "0.27.2" - -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== - -resolve-cwd@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@5.0.0, resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-path@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7" - integrity sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w== - dependencies: - http-errors "~1.6.2" - path-is-absolute "1.0.1" - -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.12.0, resolve@^1.19.0, resolve@^1.20.0: - version "1.22.6" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" - integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.10.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== - dependencies: - lowercase-keys "^2.0.0" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@3.0.2, rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rollup@^3.27.1: - version "3.29.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.2.tgz#cbc76cd5b03b9f9e93be991d23a1dff9c6d5b740" - integrity sha512-CJouHoZ27v6siztc21eEQGo0kIcE5D1gVPA571ez0mMYb25LGYGKnVNXpEj5MGlepmDWGXNjDB5q7uNiPHC11A== - optionalDependencies: - fsevents "~2.3.2" - -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@7.8.1, rxjs@^7.5.5: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -rxjs@^6.4.0, rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sanitize-html@2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6" - integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA== - dependencies: - deepmerge "^4.2.2" - escape-string-regexp "^4.0.0" - htmlparser2 "^8.0.0" - is-plain-object "^5.0.0" - parse-srcset "^1.0.2" - postcss "^8.3.11" - -sax@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" - -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== - dependencies: - loose-envify "^1.1.0" - -schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" - integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -scroll-into-view-if-needed@^2.2.20: - version "2.2.31" - resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587" - integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA== - dependencies: - compute-scroll-into-view "^1.0.20" - -selderee@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.10.0.tgz#ec83d6044d9026668dc9bd2561acfde99a4e3a1c" - integrity sha512-DEL/RW/f4qLw/NrVg97xKaEBC8IpzIG2fvxnzCp3Z4yk4jQ3MXom+Imav9wApjxX2dfS3eW7x0DXafJr85i39A== - dependencies: - parseley "^0.11.0" - -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@7.5.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@^6.0.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -sendmail@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/sendmail/-/sendmail-1.6.1.tgz#6be92fb4be70d1d9ad102030aeb1e737bd512159" - integrity sha512-lIhvnjSi5e5jL8wA1GPP6j2QVlx6JOEfmdn0QIfmuJdmXYGmJ375kcOU0NSm/34J+nypm4sa1AXrYE5w3uNIIA== - dependencies: - dkim-signer "0.2.2" - mailcomposer "3.12.0" - -sentence-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-2.1.1.tgz#1f6e2dda39c168bf92d13f86d4a918933f667ed4" - integrity sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ== - dependencies: - no-case "^2.2.0" - upper-case-first "^1.1.2" - -serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -sharp@0.32.6: - version "0.32.6" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" - integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w== - dependencies: - color "^4.2.3" - detect-libc "^2.0.2" - node-addon-api "^6.1.0" - prebuild-install "^7.1.1" - semver "^7.5.4" - simple-get "^4.0.1" - tar-fs "^3.0.4" - tunnel-agent "^0.6.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -showdown@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.9.1.tgz#134e148e75cd4623e09c21b0511977d79b5ad0ef" - integrity sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA== - dependencies: - yargs "^14.2" - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -sift@16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053" - integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ== - -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0, simple-get@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sirv@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" - integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== - dependencies: - "@polka/url" "^1.0.0-next.24" - mrmime "^2.0.0" - totalist "^3.0.0" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -slate-history@0.93.0: - version "0.93.0" - resolved "https://registry.yarnpkg.com/slate-history/-/slate-history-0.93.0.tgz#d2fad47e4e8b262ab7c86b653f5dd6d9b6d85277" - integrity sha512-Gr1GMGPipRuxIz41jD2/rbvzPj8eyar56TVMyJBvBeIpQSSjNISssvGNDYfJlSWM8eaRqf6DAcxMKzsLCYeX6g== - dependencies: - is-plain-object "^5.0.0" - -slate-react@0.98.3: - version "0.98.3" - resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.98.3.tgz#5090d269d69186f3ec2a6b5862d2645f01772eda" - integrity sha512-p1BnF9eRyRM0i5hkgOb11KgmpWLQm9Zyp6jVkOAj5fPdIGheKhg48Z7aWKrayeJ4nmRyi/NjRZz/io5hQcphmw== - dependencies: - "@juggle/resize-observer" "^3.4.0" - "@types/is-hotkey" "^0.1.1" - "@types/lodash" "^4.14.149" - direction "^1.0.3" - is-hotkey "^0.1.6" - is-plain-object "^5.0.0" - lodash "^4.17.4" - scroll-into-view-if-needed "^2.2.20" - tiny-invariant "1.0.6" - -slate@0.94.1: - version "0.94.1" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.94.1.tgz#13b0ba7d0a7eeb0ec89a87598e9111cbbd685696" - integrity sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA== - dependencies: - immer "^9.0.6" - is-plain-object "^5.0.0" - tiny-warning "^1.0.3" - -snake-case@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" - integrity sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q== - dependencies: - no-case "^2.2.0" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sort-object-keys@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" - integrity sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg== - -sort-package-json@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/sort-package-json/-/sort-package-json-2.5.1.tgz#5c0f2ce8cc8851988e5039f76b8978439439039d" - integrity sha512-vx/KoZxm8YNMUqdlw7SGTfqR5pqZ/sUfgOuRtDILiOy/3AvzhAibyUe2cY3OpLs3oRSow9up4yLVtQaM24rbDQ== - dependencies: - detect-indent "^7.0.1" - detect-newline "^4.0.0" - get-stdin "^9.0.0" - git-hooks-list "^3.0.0" - globby "^13.1.2" - is-plain-obj "^4.1.0" - sort-object-keys "^1.1.3" - -sorted-array-functions@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" - integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6, source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -spdx-correct@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" - integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.16" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz#a14f64e0954f6e25cc6587bd4f392522db0d998f" - integrity sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw== - -speedometer@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.0.0.tgz#cd671cb06752c22bca3370e2f334440be4fc62e2" - integrity sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -split2@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -statuses@2.0.1, statuses@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -strapi-provider-email-resend@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strapi-provider-email-resend/-/strapi-provider-email-resend-1.0.4.tgz#819062ecf9c142dfa3dbabba98d78c861a56d160" - integrity sha512-u40LfliYTTy5PqS9I6rV7xKfrvuA93MpJg4FEwwGseRfvH3OArNgORqUTag/zZmahYOJTxBUWFjcOhEO+SzbDA== - dependencies: - resend "0.11.1" - -stream-chain@2.2.5, stream-chain@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" - integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== - -stream-json@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.8.0.tgz#53f486b2e3b4496c506131f8d7260ba42def151c" - integrity sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw== - dependencies: - stream-chain "^2.2.5" - -stream-slice@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/stream-slice/-/stream-slice-0.1.2.tgz#2dc4f4e1b936fb13f3eb39a2def1932798d07a4b" - integrity sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA== - -streamx@^2.15.0: - version "2.15.6" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.6.tgz#28bf36997ebc7bf6c08f9eba958735231b833887" - integrity sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw== - dependencies: - fast-fifo "^1.1.0" - queue-tick "^1.0.1" - -string-argv@~0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" - integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -style-loader@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" - integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== - -style-mod@^4.0.0, style-mod@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.0.tgz#a313a14f4ae8bb4d52878c0053c4327fb787ec09" - integrity sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA== - -styled-components@5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743" - integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^0.8.8" - "@emotion/stylis" "^0.8.4" - "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1.12.0" - css-to-react-native "^3.0.0" - hoist-non-react-statics "^3.0.0" - shallowequal "^1.1.0" - supports-color "^5.5.0" - -styled-components@^6.0.7: - version "6.1.0" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.0.tgz#228e3ab9c1ee1daa4b0a06aae30df0ed14fda274" - integrity sha512-VWNfYYBuXzuLS/QYEeoPgMErP26WL+dX9//rEh80B2mmlS1yRxRxuL5eax4m6ybYEUoHWlTy2XOU32767mlMkg== - dependencies: - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/unitless" "^0.8.0" - "@types/stylis" "^4.0.2" - css-to-react-native "^3.2.0" - csstype "^3.1.2" - postcss "^8.4.31" - shallowequal "^1.1.0" - stylis "^4.3.0" - tslib "^2.5.0" - -stylis@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - -stylis@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" - integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -swap-case@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" - integrity sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ== - dependencies: - lower-case "^1.1.1" - upper-case "^1.1.1" - -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -synckit@0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== - dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" - -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-fs@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" - integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== - dependencies: - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^3.1.5" - -tar-stream@2.2.0, tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar-stream@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab" - integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg== - dependencies: - b4a "^1.6.4" - fast-fifo "^1.2.0" - streamx "^2.15.0" - -tar@6.1.13: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^4.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tarn@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" - integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== - -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.8" - -terser@^5.10.0, terser@^5.16.8: - version "5.20.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.20.0.tgz#ea42aea62578703e33def47d5c5b93c49772423e" - integrity sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -through2@^2.0.1, through2@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tildify@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" - integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== - -tiny-invariant@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" - integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== - -tiny-invariant@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" - integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== - -tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -title-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" - integrity sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q== - dependencies: - no-case "^2.2.0" - upper-case "^1.0.3" - -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -toposort@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" - integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== - -totalist@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" - integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== - -tough-cookie@^4.0.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" - integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tsscmp@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" - integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -turndown-plugin-gfm@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz#6f8678a361f35220b2bdf5619e6049add75bf1c7" - integrity sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg== - -turndown@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/turndown/-/turndown-6.0.0.tgz#c083d6109a9366be1b84b86b20af09140ea4b413" - integrity sha512-UVJBhSyRHCpNKtQ00mNWlYUM/i+tcipkb++F0PrOpt0L7EhNd0AX9mWEpL2dRFBu7LWXMp4HgAMA4OeKKnN7og== - dependencies: - jsdom "^16.2.0" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-fest@^2.18.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@^1.6.14, type-is@^1.6.16: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== - -umzug@3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/umzug/-/umzug-3.2.1.tgz#01c3a109efb037a10a317d4191be22810c37b195" - integrity sha512-XyWQowvP9CKZycKc/Zg9SYWrAWX/gJCE799AUTFqk8yC3tp44K1xWr3LoFF0MNEjClKOo1suCr5ASnoy+KltdA== - dependencies: - "@rushstack/ts-command-line" "^4.12.2" - emittery "^0.12.1" - fs-jetpack "^4.3.1" - glob "^8.0.3" - pony-cause "^2.1.2" - type-fest "^2.18.0" - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unload@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" - integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "^2.0.4" - -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - -update-browserslist-db@^1.0.11: - version "1.0.12" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.12.tgz#868ce670ac09b4a4d4c86b608701c0dee2dc41cd" - integrity sha512-tE1smlR58jxbFMtrMpFNRmsrOXlpNXss965T1CrpwuZUzUAg/TBQc94SpyhDLSzrqrJS9xTRBthnZAGcE1oaxg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -upper-case-first@^1.1.0, upper-case-first@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" - integrity sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ== - dependencies: - upper-case "^1.1.1" - -upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1, upper-case@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - -url-join@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use-callback-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5" - integrity sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w== - dependencies: - tslib "^2.0.0" - -use-isomorphic-layout-effect@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-sidecar@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" - integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== - dependencies: - detect-node-es "^1.1.0" - tslib "^2.0.0" - -use-sync-external-store@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - integrity sha512-aggiKfEEubv3UwRNqTzLInZpAOmKzwdHqEBmW/hBA/mt99eg+b4VrX6i+IRLxU8+WJYfa33rGwRseg4eElUgsQ== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@^0.12.4: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - -utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -utils-merge@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" - integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8flags@^2.0.10: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - integrity sha512-SKfhk/LlaXzvtowJabLZwD4K6SGRYeoxA7KJeISlUMAB/NT4CBkZjMq3WceX2Ckm4llwqYVo8TICgsDYCBU2tA== - dependencies: - user-home "^1.1.1" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - -vanilla-colorful@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz#3fb1f4b9f15b797e20fd1ce8e0364f33b073f4a2" - integrity sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg== - -vary@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vite@4.4.9: - version "4.4.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d" - integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA== - dependencies: - esbuild "^0.18.10" - postcss "^8.4.27" - rollup "^3.27.1" - optionalDependencies: - fsevents "~2.3.2" - -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-keyname@^2.2.4: - version "2.2.8" - resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" - integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" - -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -webpack-bundle-analyzer@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454" - integrity sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ== - dependencies: - "@discoveryjs/json-ext" "0.5.7" - acorn "^8.0.4" - acorn-walk "^8.0.0" - commander "^7.2.0" - debounce "^1.2.1" - escape-string-regexp "^4.0.0" - gzip-size "^6.0.0" - html-escaper "^2.0.2" - is-plain-object "^5.0.0" - opener "^1.5.2" - picocolors "^1.0.0" - sirv "^2.0.3" - ws "^7.3.1" - -webpack-dev-middleware@6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz#6bbc257ec83ae15522de7a62f995630efde7cc3d" - integrity sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ== - dependencies: - colorette "^2.0.10" - memfs "^3.4.12" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-hot-middleware@2.25.4: - version "2.25.4" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz#d8bc9e9cb664fc3105c8e83d2b9ed436bee4e193" - integrity sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w== - dependencies: - ansi-html-community "0.0.8" - html-entities "^2.1.0" - strip-ansi "^6.0.0" - -webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@^5.88.1: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which-typed-array@^1.1.11, which-typed-array@^1.1.2: - version "1.1.11" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== - 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" - -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.10.0.tgz#d033cb7bd3ced026fed13bf9d92c55b903116803" - integrity sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== - -ws@^7.3.1, ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xml2js@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" - integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^15.0.1: - version "15.0.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.3.tgz#316e263d5febe8b38eef61ac092b33dfcc9b1115" - integrity sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^14.2: - version "14.2.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" - integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== - dependencies: - cliui "^5.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^15.0.1" - -yargs@^16.1.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -ylru@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" - integrity sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yup@0.32.9: - version "0.32.9" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.9.tgz#9367bec6b1b0e39211ecbca598702e106019d872" - integrity sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg== - dependencies: - "@babel/runtime" "^7.10.5" - "@types/lodash" "^4.14.165" - lodash "^4.17.20" - lodash-es "^4.17.15" - nanoclone "^0.2.1" - property-expr "^2.0.4" - toposort "^2.0.2" diff --git a/backend/.gitignore b/core/.gitignore similarity index 100% rename from backend/.gitignore rename to core/.gitignore diff --git a/backend/core/.python-version b/core/.python-version similarity index 100% rename from backend/core/.python-version rename to core/.python-version diff --git a/backend/core/CHANGELOG.md b/core/CHANGELOG.md similarity index 100% rename from backend/core/CHANGELOG.md rename to core/CHANGELOG.md diff --git a/backend/core/Dockerfile.test b/core/Dockerfile.test similarity index 100% rename from backend/core/Dockerfile.test rename to core/Dockerfile.test diff --git a/core/README.md b/core/README.md new file mode 100644 index 000000000..6eff88613 --- /dev/null +++ b/core/README.md @@ -0,0 +1,3 @@ +# quivr-core package + +The RAG of Quivr.com diff --git a/backend/core/pyproject.toml b/core/pyproject.toml similarity index 96% rename from backend/core/pyproject.toml rename to core/pyproject.toml index e5544aac8..b86ce4bc5 100644 --- a/backend/core/pyproject.toml +++ b/core/pyproject.toml @@ -27,7 +27,6 @@ all = [ "unstructured[epub,docx,odt,doc,pptx,ppt,xlsx,md]>=0.15.5", "faiss-cpu>=1.8.0.post1", "docx2txt>=0.8", - "megaparse" ] megaparse = [ @@ -86,6 +85,3 @@ markers = [ module = "yaml" ignore_missing_imports = true -[[tool.rye.sources]] -name = "megaparse" -path = "./MegaParse" diff --git a/backend/core/quivr_core/__init__.py b/core/quivr_core/__init__.py similarity index 100% rename from backend/core/quivr_core/__init__.py rename to core/quivr_core/__init__.py diff --git a/backend/core/quivr_core/base_config.py b/core/quivr_core/base_config.py similarity index 100% rename from backend/core/quivr_core/base_config.py rename to core/quivr_core/base_config.py diff --git a/backend/core/quivr_core/brain/__init__.py b/core/quivr_core/brain/__init__.py similarity index 100% rename from backend/core/quivr_core/brain/__init__.py rename to core/quivr_core/brain/__init__.py diff --git a/backend/core/quivr_core/brain/brain.py b/core/quivr_core/brain/brain.py similarity index 100% rename from backend/core/quivr_core/brain/brain.py rename to core/quivr_core/brain/brain.py diff --git a/backend/core/quivr_core/brain/brain_defaults.py b/core/quivr_core/brain/brain_defaults.py similarity index 100% rename from backend/core/quivr_core/brain/brain_defaults.py rename to core/quivr_core/brain/brain_defaults.py diff --git a/backend/core/quivr_core/brain/info.py b/core/quivr_core/brain/info.py similarity index 100% rename from backend/core/quivr_core/brain/info.py rename to core/quivr_core/brain/info.py diff --git a/backend/core/quivr_core/brain/serialization.py b/core/quivr_core/brain/serialization.py similarity index 100% rename from backend/core/quivr_core/brain/serialization.py rename to core/quivr_core/brain/serialization.py diff --git a/backend/core/quivr_core/chat.py b/core/quivr_core/chat.py similarity index 100% rename from backend/core/quivr_core/chat.py rename to core/quivr_core/chat.py diff --git a/backend/core/quivr_core/config.py b/core/quivr_core/config.py similarity index 100% rename from backend/core/quivr_core/config.py rename to core/quivr_core/config.py diff --git a/backend/core/quivr_core/files/__init__.py b/core/quivr_core/files/__init__.py similarity index 100% rename from backend/core/quivr_core/files/__init__.py rename to core/quivr_core/files/__init__.py diff --git a/backend/core/quivr_core/files/file.py b/core/quivr_core/files/file.py similarity index 100% rename from backend/core/quivr_core/files/file.py rename to core/quivr_core/files/file.py diff --git a/backend/core/quivr_core/llm/__init__.py b/core/quivr_core/llm/__init__.py similarity index 100% rename from backend/core/quivr_core/llm/__init__.py rename to core/quivr_core/llm/__init__.py diff --git a/backend/core/quivr_core/llm/llm_endpoint.py b/core/quivr_core/llm/llm_endpoint.py similarity index 100% rename from backend/core/quivr_core/llm/llm_endpoint.py rename to core/quivr_core/llm/llm_endpoint.py diff --git a/backend/core/quivr_core/models.py b/core/quivr_core/models.py similarity index 100% rename from backend/core/quivr_core/models.py rename to core/quivr_core/models.py diff --git a/backend/api/quivr_api/middlewares/__init__.py b/core/quivr_core/processor/__init__.py similarity index 100% rename from backend/api/quivr_api/middlewares/__init__.py rename to core/quivr_core/processor/__init__.py diff --git a/backend/api/quivr_api/models/__init__.py b/core/quivr_core/processor/implementations/__init__.py similarity index 100% rename from backend/api/quivr_api/models/__init__.py rename to core/quivr_core/processor/implementations/__init__.py diff --git a/backend/core/quivr_core/processor/implementations/default.py b/core/quivr_core/processor/implementations/default.py similarity index 100% rename from backend/core/quivr_core/processor/implementations/default.py rename to core/quivr_core/processor/implementations/default.py diff --git a/backend/core/quivr_core/processor/implementations/megaparse_processor.py b/core/quivr_core/processor/implementations/megaparse_processor.py similarity index 100% rename from backend/core/quivr_core/processor/implementations/megaparse_processor.py rename to core/quivr_core/processor/implementations/megaparse_processor.py diff --git a/backend/core/quivr_core/processor/implementations/simple_txt_processor.py b/core/quivr_core/processor/implementations/simple_txt_processor.py similarity index 100% rename from backend/core/quivr_core/processor/implementations/simple_txt_processor.py rename to core/quivr_core/processor/implementations/simple_txt_processor.py diff --git a/backend/core/quivr_core/processor/implementations/tika_processor.py b/core/quivr_core/processor/implementations/tika_processor.py similarity index 100% rename from backend/core/quivr_core/processor/implementations/tika_processor.py rename to core/quivr_core/processor/implementations/tika_processor.py diff --git a/backend/core/quivr_core/processor/megaparse/Converter.py b/core/quivr_core/processor/megaparse/Converter.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/Converter.py rename to core/quivr_core/processor/megaparse/Converter.py diff --git a/backend/core/quivr_core/processor/megaparse/__init__.py b/core/quivr_core/processor/megaparse/__init__.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/__init__.py rename to core/quivr_core/processor/megaparse/__init__.py diff --git a/backend/core/quivr_core/processor/megaparse/config.py b/core/quivr_core/processor/megaparse/config.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/config.py rename to core/quivr_core/processor/megaparse/config.py diff --git a/backend/core/quivr_core/processor/megaparse/markdown_processor.py b/core/quivr_core/processor/megaparse/markdown_processor.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/markdown_processor.py rename to core/quivr_core/processor/megaparse/markdown_processor.py diff --git a/backend/api/quivr_api/models/databases/__init__.py b/core/quivr_core/processor/megaparse/multimodal_convertor/__init__.py similarity index 100% rename from backend/api/quivr_api/models/databases/__init__.py rename to core/quivr_core/processor/megaparse/multimodal_convertor/__init__.py diff --git a/backend/core/quivr_core/processor/megaparse/multimodal_convertor/megaparse_vision.py b/core/quivr_core/processor/megaparse/multimodal_convertor/megaparse_vision.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/multimodal_convertor/megaparse_vision.py rename to core/quivr_core/processor/megaparse/multimodal_convertor/megaparse_vision.py diff --git a/backend/core/quivr_core/processor/megaparse/unstructured_convertor.py b/core/quivr_core/processor/megaparse/unstructured_convertor.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/unstructured_convertor.py rename to core/quivr_core/processor/megaparse/unstructured_convertor.py diff --git a/backend/core/quivr_core/processor/megaparse/utils.py b/core/quivr_core/processor/megaparse/utils.py similarity index 100% rename from backend/core/quivr_core/processor/megaparse/utils.py rename to core/quivr_core/processor/megaparse/utils.py diff --git a/backend/core/quivr_core/processor/processor_base.py b/core/quivr_core/processor/processor_base.py similarity index 100% rename from backend/core/quivr_core/processor/processor_base.py rename to core/quivr_core/processor/processor_base.py diff --git a/backend/core/quivr_core/processor/registry.py b/core/quivr_core/processor/registry.py similarity index 100% rename from backend/core/quivr_core/processor/registry.py rename to core/quivr_core/processor/registry.py diff --git a/backend/core/quivr_core/processor/splitter.py b/core/quivr_core/processor/splitter.py similarity index 100% rename from backend/core/quivr_core/processor/splitter.py rename to core/quivr_core/processor/splitter.py diff --git a/backend/core/quivr_core/prompts.py b/core/quivr_core/prompts.py similarity index 100% rename from backend/core/quivr_core/prompts.py rename to core/quivr_core/prompts.py diff --git a/backend/core/quivr_core/quivr_rag.py b/core/quivr_core/quivr_rag.py similarity index 100% rename from backend/core/quivr_core/quivr_rag.py rename to core/quivr_core/quivr_rag.py diff --git a/backend/core/quivr_core/quivr_rag_langgraph.py b/core/quivr_core/quivr_rag_langgraph.py similarity index 100% rename from backend/core/quivr_core/quivr_rag_langgraph.py rename to core/quivr_core/quivr_rag_langgraph.py diff --git a/backend/api/quivr_api/modules/__init__.py b/core/quivr_core/storage/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/__init__.py rename to core/quivr_core/storage/__init__.py diff --git a/backend/core/quivr_core/storage/file.py b/core/quivr_core/storage/file.py similarity index 100% rename from backend/core/quivr_core/storage/file.py rename to core/quivr_core/storage/file.py diff --git a/backend/core/quivr_core/storage/local_storage.py b/core/quivr_core/storage/local_storage.py similarity index 100% rename from backend/core/quivr_core/storage/local_storage.py rename to core/quivr_core/storage/local_storage.py diff --git a/backend/core/quivr_core/storage/storage_base.py b/core/quivr_core/storage/storage_base.py similarity index 100% rename from backend/core/quivr_core/storage/storage_base.py rename to core/quivr_core/storage/storage_base.py diff --git a/backend/core/quivr_core/utils.py b/core/quivr_core/utils.py similarity index 100% rename from backend/core/quivr_core/utils.py rename to core/quivr_core/utils.py diff --git a/core/requirements-dev.lock b/core/requirements-dev.lock new file mode 100644 index 000000000..a37ede267 --- /dev/null +++ b/core/requirements-dev.lock @@ -0,0 +1,321 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false +# universal: false + +-e file:. +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 + # via aiohttp +aiohttp==3.10.10 + # via langchain + # via langchain-community +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.36.1 + # via langchain-anthropic +anyio==4.6.2.post1 + # via anthropic + # via httpx +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via aiohttp +black==24.10.0 + # via flake8-black +certifi==2024.8.30 + # via httpcore + # via httpx + # via requests +cfgv==3.4.0 + # via pre-commit +charset-normalizer==3.4.0 + # via requests +click==8.1.7 + # via black +comm==0.2.2 + # via ipykernel +dataclasses-json==0.6.7 + # via langchain-community +debugpy==1.8.7 + # via ipykernel +decorator==5.1.1 + # via ipython +defusedxml==0.7.1 + # via langchain-anthropic +distlib==0.3.9 + # via virtualenv +distro==1.9.0 + # via anthropic +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +filelock==3.16.1 + # via huggingface-hub + # via transformers + # via virtualenv +flake8==7.1.1 + # via flake8-black +flake8-black==0.3.6 +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +fsspec==2024.9.0 + # via huggingface-hub +greenlet==3.1.1 + # via sqlalchemy +h11==0.14.0 + # via httpcore +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via anthropic + # via langgraph-sdk + # via langsmith + # via quivr-core +httpx-sse==0.4.0 + # via langgraph-sdk +huggingface-hub==0.25.2 + # via tokenizers + # via transformers +identify==2.6.1 + # via pre-commit +idna==3.10 + # via anyio + # via httpx + # via requests + # via yarl +iniconfig==2.0.0 + # via pytest +ipykernel==6.29.5 +ipython==8.28.0 + # via ipykernel +jedi==0.19.1 + # via ipython +jiter==0.6.1 + # via anthropic +jsonpatch==1.33 + # via langchain-core +jsonpointer==3.0.0 + # via jsonpatch +jupyter-client==8.6.3 + # via ipykernel +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client +langchain==0.2.16 + # via langchain-community + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-community==0.2.17 + # via quivr-core +langchain-core==0.2.41 + # via langchain + # via langchain-anthropic + # via langchain-community + # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint + # via quivr-core +langchain-text-splitters==0.2.4 + # via langchain +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 + # via langchain + # via langchain-community + # via langchain-core +markdown-it-py==3.0.0 + # via rich +marshmallow==3.22.0 + # via dataclasses-json +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mccabe==0.7.0 + # via flake8 +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 + # via aiohttp + # via yarl +mypy==1.12.0 +mypy-extensions==1.0.0 + # via black + # via mypy + # via typing-inspect +nest-asyncio==1.6.0 + # via ipykernel +nodeenv==1.9.1 + # via pre-commit +numpy==1.26.4 + # via langchain + # via langchain-community + # via transformers +orjson==3.10.7 + # via langgraph-sdk + # via langsmith +packaging==24.1 + # via black + # via huggingface-hub + # via ipykernel + # via langchain-core + # via marshmallow + # via pytest + # via transformers +parso==0.8.4 + # via jedi +pathspec==0.12.1 + # via black +pexpect==4.9.0 + # via ipython +platformdirs==4.3.6 + # via black + # via jupyter-core + # via virtualenv +pluggy==1.5.0 + # via pytest +pre-commit==4.0.1 +prompt-toolkit==3.0.48 + # via ipython +propcache==0.2.0 + # via yarl +protobuf==5.28.2 + # via transformers +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +py-cpuinfo==9.0.0 + # via pytest-benchmark +pycodestyle==2.12.1 + # via flake8 +pydantic==2.9.2 + # via anthropic + # via langchain + # via langchain-core + # via langsmith + # via quivr-core +pydantic-core==2.23.4 + # via pydantic +pyflakes==3.2.0 + # via flake8 +pygments==2.18.0 + # via ipython + # via rich +pytest==8.3.3 + # via pytest-asyncio + # via pytest-benchmark + # via pytest-xdist +pytest-asyncio==0.24.0 +pytest-benchmark==4.0.0 +pytest-xdist==3.6.1 +python-dateutil==2.9.0.post0 + # via jupyter-client +pyyaml==6.0.2 + # via huggingface-hub + # via langchain + # via langchain-community + # via langchain-core + # via pre-commit + # via transformers +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +regex==2024.9.11 + # via tiktoken + # via transformers +requests==2.32.3 + # via huggingface-hub + # via langchain + # via langchain-community + # via langsmith + # via requests-toolbelt + # via tiktoken + # via transformers +requests-toolbelt==1.0.0 + # via langsmith +rich==13.9.2 + # via quivr-core +ruff==0.6.9 +safetensors==0.4.5 + # via transformers +sentencepiece==0.2.0 + # via transformers +six==1.16.0 + # via asttokens + # via python-dateutil +sniffio==1.3.1 + # via anthropic + # via anyio + # via httpx +sqlalchemy==2.0.36 + # via langchain + # via langchain-community +stack-data==0.6.3 + # via ipython +tenacity==8.5.0 + # via langchain + # via langchain-community + # via langchain-core +tiktoken==0.8.0 + # via quivr-core +tokenizers==0.20.1 + # via anthropic + # via transformers +tornado==6.4.1 + # via ipykernel + # via jupyter-client +tqdm==4.66.5 + # via huggingface-hub + # via transformers +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline +transformers==4.45.2 + # via quivr-core +types-pyyaml==6.0.12.20240917 + # via quivr-core +typing-extensions==4.12.2 + # via anthropic + # via huggingface-hub + # via ipython + # via langchain-core + # via mypy + # via pydantic + # via pydantic-core + # via sqlalchemy + # via typing-inspect +typing-inspect==0.9.0 + # via dataclasses-json +urllib3==2.2.3 + # via requests +virtualenv==20.26.6 + # via pre-commit +wcwidth==0.2.13 + # via prompt-toolkit +yarl==1.15.3 + # via aiohttp diff --git a/core/requirements.lock b/core/requirements.lock new file mode 100644 index 000000000..405584c9b --- /dev/null +++ b/core/requirements.lock @@ -0,0 +1,205 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false +# universal: false + +-e file:. +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 + # via aiohttp +aiohttp==3.10.10 + # via langchain + # via langchain-community +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.36.1 + # via langchain-anthropic +anyio==4.6.2.post1 + # via anthropic + # via httpx +attrs==24.2.0 + # via aiohttp +certifi==2024.8.30 + # via httpcore + # via httpx + # via requests +charset-normalizer==3.4.0 + # via requests +dataclasses-json==0.6.7 + # via langchain-community +defusedxml==0.7.1 + # via langchain-anthropic +distro==1.9.0 + # via anthropic +filelock==3.16.1 + # via huggingface-hub + # via transformers +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +fsspec==2024.9.0 + # via huggingface-hub +greenlet==3.1.1 + # via sqlalchemy +h11==0.14.0 + # via httpcore +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via anthropic + # via langgraph-sdk + # via langsmith + # via quivr-core +httpx-sse==0.4.0 + # via langgraph-sdk +huggingface-hub==0.25.2 + # via tokenizers + # via transformers +idna==3.10 + # via anyio + # via httpx + # via requests + # via yarl +jiter==0.6.1 + # via anthropic +jsonpatch==1.33 + # via langchain-core +jsonpointer==3.0.0 + # via jsonpatch +langchain==0.2.16 + # via langchain-community + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-community==0.2.17 + # via quivr-core +langchain-core==0.2.41 + # via langchain + # via langchain-anthropic + # via langchain-community + # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint + # via quivr-core +langchain-text-splitters==0.2.4 + # via langchain +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 + # via langchain + # via langchain-community + # via langchain-core +markdown-it-py==3.0.0 + # via rich +marshmallow==3.22.0 + # via dataclasses-json +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 + # via aiohttp + # via yarl +mypy-extensions==1.0.0 + # via typing-inspect +numpy==1.26.4 + # via langchain + # via langchain-community + # via transformers +orjson==3.10.7 + # via langgraph-sdk + # via langsmith +packaging==24.1 + # via huggingface-hub + # via langchain-core + # via marshmallow + # via transformers +propcache==0.2.0 + # via yarl +protobuf==5.28.2 + # via transformers +pydantic==2.9.2 + # via anthropic + # via langchain + # via langchain-core + # via langsmith + # via quivr-core +pydantic-core==2.23.4 + # via pydantic +pygments==2.18.0 + # via rich +pyyaml==6.0.2 + # via huggingface-hub + # via langchain + # via langchain-community + # via langchain-core + # via transformers +regex==2024.9.11 + # via tiktoken + # via transformers +requests==2.32.3 + # via huggingface-hub + # via langchain + # via langchain-community + # via langsmith + # via requests-toolbelt + # via tiktoken + # via transformers +requests-toolbelt==1.0.0 + # via langsmith +rich==13.9.2 + # via quivr-core +safetensors==0.4.5 + # via transformers +sentencepiece==0.2.0 + # via transformers +sniffio==1.3.1 + # via anthropic + # via anyio + # via httpx +sqlalchemy==2.0.36 + # via langchain + # via langchain-community +tenacity==8.5.0 + # via langchain + # via langchain-community + # via langchain-core +tiktoken==0.8.0 + # via quivr-core +tokenizers==0.20.1 + # via anthropic + # via transformers +tqdm==4.66.5 + # via huggingface-hub + # via transformers +transformers==4.45.2 + # via quivr-core +types-pyyaml==6.0.12.20240917 + # via quivr-core +typing-extensions==4.12.2 + # via anthropic + # via huggingface-hub + # via langchain-core + # via pydantic + # via pydantic-core + # via sqlalchemy + # via typing-inspect +typing-inspect==0.9.0 + # via dataclasses-json +urllib3==2.2.3 + # via requests +yarl==1.15.3 + # via aiohttp diff --git a/backend/core/scripts/run_tests.sh b/core/scripts/run_tests.sh similarity index 100% rename from backend/core/scripts/run_tests.sh rename to core/scripts/run_tests.sh diff --git a/backend/core/scripts/run_tests_buildx.sh b/core/scripts/run_tests_buildx.sh similarity index 100% rename from backend/core/scripts/run_tests_buildx.sh rename to core/scripts/run_tests_buildx.sh diff --git a/backend/api/quivr_api/modules/analytics/__init__.py b/core/tests/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/analytics/__init__.py rename to core/tests/__init__.py diff --git a/backend/core/tests/chunk_stream_fixture.jsonl b/core/tests/chunk_stream_fixture.jsonl similarity index 100% rename from backend/core/tests/chunk_stream_fixture.jsonl rename to core/tests/chunk_stream_fixture.jsonl diff --git a/backend/core/tests/conftest.py b/core/tests/conftest.py similarity index 100% rename from backend/core/tests/conftest.py rename to core/tests/conftest.py diff --git a/backend/core/tests/fixture_chunks.py b/core/tests/fixture_chunks.py similarity index 100% rename from backend/core/tests/fixture_chunks.py rename to core/tests/fixture_chunks.py diff --git a/backend/api/quivr_api/modules/analytics/controller/__init__.py b/core/tests/processor/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/analytics/controller/__init__.py rename to core/tests/processor/__init__.py diff --git a/backend/api/quivr_api/modules/analytics/entity/__init__.py b/core/tests/processor/community/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/analytics/entity/__init__.py rename to core/tests/processor/community/__init__.py diff --git a/backend/core/tests/processor/community/csv_processor.py b/core/tests/processor/community/csv_processor.py similarity index 100% rename from backend/core/tests/processor/community/csv_processor.py rename to core/tests/processor/community/csv_processor.py diff --git a/backend/core/tests/processor/community/test_markdown_processor.py b/core/tests/processor/community/test_markdown_processor.py similarity index 100% rename from backend/core/tests/processor/community/test_markdown_processor.py rename to core/tests/processor/community/test_markdown_processor.py diff --git a/backend/core/tests/processor/data/dummy.pdf b/core/tests/processor/data/dummy.pdf similarity index 100% rename from backend/core/tests/processor/data/dummy.pdf rename to core/tests/processor/data/dummy.pdf diff --git a/backend/core/tests/processor/data/guidelines_code.md b/core/tests/processor/data/guidelines_code.md similarity index 100% rename from backend/core/tests/processor/data/guidelines_code.md rename to core/tests/processor/data/guidelines_code.md diff --git a/backend/api/quivr_api/modules/analytics/repository/__init__.py b/core/tests/processor/docx/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/analytics/repository/__init__.py rename to core/tests/processor/docx/__init__.py diff --git a/backend/core/tests/processor/docx/demo.docx b/core/tests/processor/docx/demo.docx similarity index 100% rename from backend/core/tests/processor/docx/demo.docx rename to core/tests/processor/docx/demo.docx diff --git a/backend/core/tests/processor/docx/test_docx.py b/core/tests/processor/docx/test_docx.py similarity index 100% rename from backend/core/tests/processor/docx/test_docx.py rename to core/tests/processor/docx/test_docx.py diff --git a/backend/api/quivr_api/modules/analytics/service/__init__.py b/core/tests/processor/epub/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/analytics/service/__init__.py rename to core/tests/processor/epub/__init__.py diff --git a/backend/core/tests/processor/epub/page-blanche.epub b/core/tests/processor/epub/page-blanche.epub similarity index 100% rename from backend/core/tests/processor/epub/page-blanche.epub rename to core/tests/processor/epub/page-blanche.epub diff --git a/backend/core/tests/processor/epub/sway.epub b/core/tests/processor/epub/sway.epub similarity index 100% rename from backend/core/tests/processor/epub/sway.epub rename to core/tests/processor/epub/sway.epub diff --git a/backend/core/tests/processor/epub/test_epub_processor.py b/core/tests/processor/epub/test_epub_processor.py similarity index 100% rename from backend/core/tests/processor/epub/test_epub_processor.py rename to core/tests/processor/epub/test_epub_processor.py diff --git a/backend/api/quivr_api/modules/api_key/__init__.py b/core/tests/processor/odt/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/api_key/__init__.py rename to core/tests/processor/odt/__init__.py diff --git a/backend/core/tests/processor/odt/bad_odt.odt b/core/tests/processor/odt/bad_odt.odt similarity index 100% rename from backend/core/tests/processor/odt/bad_odt.odt rename to core/tests/processor/odt/bad_odt.odt diff --git a/backend/core/tests/processor/odt/sample.odt b/core/tests/processor/odt/sample.odt similarity index 100% rename from backend/core/tests/processor/odt/sample.odt rename to core/tests/processor/odt/sample.odt diff --git a/backend/core/tests/processor/odt/test_odt.py b/core/tests/processor/odt/test_odt.py similarity index 100% rename from backend/core/tests/processor/odt/test_odt.py rename to core/tests/processor/odt/test_odt.py diff --git a/backend/api/quivr_api/modules/api_key/entity/__init__.py b/core/tests/processor/pdf/__init__.py similarity index 100% rename from backend/api/quivr_api/modules/api_key/entity/__init__.py rename to core/tests/processor/pdf/__init__.py diff --git a/backend/core/tests/processor/pdf/sample.pdf b/core/tests/processor/pdf/sample.pdf similarity index 100% rename from backend/core/tests/processor/pdf/sample.pdf rename to core/tests/processor/pdf/sample.pdf diff --git a/backend/core/tests/processor/pdf/test_megaparse_pdf_processor.py b/core/tests/processor/pdf/test_megaparse_pdf_processor.py similarity index 100% rename from backend/core/tests/processor/pdf/test_megaparse_pdf_processor.py rename to core/tests/processor/pdf/test_megaparse_pdf_processor.py diff --git a/backend/core/tests/processor/pdf/test_unstructured_pdf_processor.py b/core/tests/processor/pdf/test_unstructured_pdf_processor.py similarity index 100% rename from backend/core/tests/processor/pdf/test_unstructured_pdf_processor.py rename to core/tests/processor/pdf/test_unstructured_pdf_processor.py diff --git a/backend/core/tests/processor/test_default_implementations.py b/core/tests/processor/test_default_implementations.py similarity index 100% rename from backend/core/tests/processor/test_default_implementations.py rename to core/tests/processor/test_default_implementations.py diff --git a/backend/core/tests/processor/test_registry.py b/core/tests/processor/test_registry.py similarity index 100% rename from backend/core/tests/processor/test_registry.py rename to core/tests/processor/test_registry.py diff --git a/backend/core/tests/processor/test_simple_txt_processor.py b/core/tests/processor/test_simple_txt_processor.py similarity index 100% rename from backend/core/tests/processor/test_simple_txt_processor.py rename to core/tests/processor/test_simple_txt_processor.py diff --git a/backend/core/tests/processor/test_tika_processor.py b/core/tests/processor/test_tika_processor.py similarity index 100% rename from backend/core/tests/processor/test_tika_processor.py rename to core/tests/processor/test_tika_processor.py diff --git a/backend/core/tests/processor/test_txt_processor.py b/core/tests/processor/test_txt_processor.py similarity index 100% rename from backend/core/tests/processor/test_txt_processor.py rename to core/tests/processor/test_txt_processor.py diff --git a/backend/core/tests/rag_config.yaml b/core/tests/rag_config.yaml similarity index 100% rename from backend/core/tests/rag_config.yaml rename to core/tests/rag_config.yaml diff --git a/backend/core/tests/rag_config_workflow.yaml b/core/tests/rag_config_workflow.yaml similarity index 100% rename from backend/core/tests/rag_config_workflow.yaml rename to core/tests/rag_config_workflow.yaml diff --git a/backend/core/tests/test_brain.py b/core/tests/test_brain.py similarity index 100% rename from backend/core/tests/test_brain.py rename to core/tests/test_brain.py diff --git a/backend/core/tests/test_chat_history.py b/core/tests/test_chat_history.py similarity index 100% rename from backend/core/tests/test_chat_history.py rename to core/tests/test_chat_history.py diff --git a/backend/core/tests/test_config.py b/core/tests/test_config.py similarity index 100% rename from backend/core/tests/test_config.py rename to core/tests/test_config.py diff --git a/backend/core/tests/test_llm_endpoint.py b/core/tests/test_llm_endpoint.py similarity index 100% rename from backend/core/tests/test_llm_endpoint.py rename to core/tests/test_llm_endpoint.py diff --git a/backend/core/tests/test_quivr_file.py b/core/tests/test_quivr_file.py similarity index 100% rename from backend/core/tests/test_quivr_file.py rename to core/tests/test_quivr_file.py diff --git a/backend/core/tests/test_quivr_rag.py b/core/tests/test_quivr_rag.py similarity index 100% rename from backend/core/tests/test_quivr_rag.py rename to core/tests/test_quivr_rag.py diff --git a/backend/core/tests/test_utils.py b/core/tests/test_utils.py similarity index 100% rename from backend/core/tests/test_utils.py rename to core/tests/test_utils.py diff --git a/backend/core/tox.ini b/core/tox.ini similarity index 100% rename from backend/core/tox.ini rename to core/tox.ini diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index 2f137a2d6..000000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,104 +0,0 @@ -version: "3.8" - -services: - backend-api: - image: backend-api:latest - extra_hosts: - - "host.docker.internal:host-gateway" - pull_policy: never - env_file: - - .env - build: - context: backend - dockerfile: Dockerfile.dev - cache_from: - - backend-api:latest - args: - - DEV_MODE=true - container_name: backend-api - volumes: - - ./backend/:/app/ - command: > - /bin/bash -c "python -m uvicorn quivr_api.main:app --host 0.0.0.0 --log-level info --reload --port 5050" - restart: always - ports: - - 5050:5050 - - 5678:5678 # debug port - - notifier: - pull_policy: never - image: backend-api:latest - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - container_name: notifier - volumes: - - ./backend/:/app/ - command: > - /bin/bash -c "python /app/worker/quivr_worker/celery_monitor.py" - restart: always - depends_on: - - redis - - worker - - worker: - pull_policy: never - image: backend-api:latest - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - container_name: worker - volumes: - - ./backend/:/app/ - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker worker -l info -E -P solo" - restart: always - depends_on: - - redis - - redis: - image: redis:latest - container_name: redis - extra_hosts: - - "host.docker.internal:host-gateway" - restart: always - ports: - - 6379:6379 - - beat: - image: backend-api:latest - pull_policy: never - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - container_name: beat - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker beat -l info" - volumes: - - ./backend/:/app/ - restart: always - depends_on: - - redis - - flower: - image: backend-api:latest - pull_policy: never - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - container_name: flower - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker flower -l info --port=5555" - volumes: - - ./backend/:/app/ - restart: always - depends_on: - - redis - - worker - - beat - ports: - - 5555:5555 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 2c6d4c34e..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,115 +0,0 @@ -version: "3.8" - -services: - frontend: - image: quivr-frontend-prebuilt - pull_policy: never - build: - context: frontend - dockerfile: Dockerfile - args: - - NEXT_PUBLIC_ENV=local - - NEXT_PUBLIC_BACKEND_URL=${NEXT_PUBLIC_BACKEND_URL} - - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} - - NEXT_PUBLIC_CMS_URL=${NEXT_PUBLIC_CMS_URL} - - NEXT_PUBLIC_FRONTEND_URL=${NEXT_PUBLIC_FRONTEND_URL} - - NEXT_PUBLIC_AUTH_MODES=${NEXT_PUBLIC_AUTH_MODES} - container_name: web - depends_on: - - backend-api - restart: always - ports: - - 3000:3000 - - backend-api: - image: stangirard/quivr-backend-prebuilt:latest - pull_policy: if_not_present - env_file: - - .env - build: - context: backend - dockerfile: Dockerfile - container_name: backend-api - extra_hosts: - - "host.docker.internal:host-gateway" - healthcheck: - test: [ "CMD", "curl", "http://localhost:5050/healthz" ] - command: > - /bin/bash -c "python -m uvicorn quivr_api.main:app --host 0.0.0.0 --log-level info --reload --port 5050 --loop uvloop" - restart: always - ports: - - 5050:5050 - - worker: - pull_policy: if_not_present - image: stangirard/quivr-backend-prebuilt:latest - env_file: - - .env - container_name: worker - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker worker -l info" - restart: always - depends_on: - - redis - - notifier: - pull_policy: never - image: stangirard/quivr-backend-prebuilt:latest - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - container_name: notifier - command: > - /bin/bash -c "python /app/worker/quivr_worker/celery_monitor.py" - restart: always - depends_on: - - redis - - worker - - beat: - image: stangirard/quivr-backend-prebuilt:latest - pull_policy: if_not_present - env_file: - - .env - container_name: beat - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker beat -l info" - restart: always - depends_on: - - - redis - - flower: - image: stangirard/quivr-backend-prebuilt:latest - pull_policy: if_not_present - env_file: - - .env - container_name: flower - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/bash -c "python -m celery -A quivr_worker.celery_worker flower -l info --port=5555" - restart: always - depends_on: - - redis - - worker - - beat - ports: - - 5555:5555 - - redis: - image: redis:latest@sha256:a7cee7c8178ff9b5297cb109e6240f5072cdaaafd775ce6b586c3c704b06458e - container_name: redis - restart: always - ports: - - 6379:6379 - -networks: - quivr-network: - driver: bridge diff --git a/backend/api/.gitignore b/docs/.gitignore similarity index 100% rename from backend/api/.gitignore rename to docs/.gitignore diff --git a/backend/docs/.python-version b/docs/.python-version similarity index 100% rename from backend/docs/.python-version rename to docs/.python-version diff --git a/backend/docs/README.md b/docs/README.md similarity index 100% rename from backend/docs/README.md rename to docs/README.md diff --git a/backend/docs/docs/brain/brain.md b/docs/docs/brain/brain.md similarity index 100% rename from backend/docs/docs/brain/brain.md rename to docs/docs/brain/brain.md diff --git a/backend/docs/docs/brain/chat.md b/docs/docs/brain/chat.md similarity index 100% rename from backend/docs/docs/brain/chat.md rename to docs/docs/brain/chat.md diff --git a/backend/docs/docs/brain/index.md b/docs/docs/brain/index.md similarity index 100% rename from backend/docs/docs/brain/index.md rename to docs/docs/brain/index.md diff --git a/backend/docs/docs/config/base_config.md b/docs/docs/config/base_config.md similarity index 100% rename from backend/docs/docs/config/base_config.md rename to docs/docs/config/base_config.md diff --git a/backend/docs/docs/config/config.md b/docs/docs/config/config.md similarity index 100% rename from backend/docs/docs/config/config.md rename to docs/docs/config/config.md diff --git a/backend/docs/docs/config/index.md b/docs/docs/config/index.md similarity index 100% rename from backend/docs/docs/config/index.md rename to docs/docs/config/index.md diff --git a/backend/docs/docs/css/style.css b/docs/docs/css/style.css similarity index 100% rename from backend/docs/docs/css/style.css rename to docs/docs/css/style.css diff --git a/backend/docs/docs/examples/custom_storage.md b/docs/docs/examples/custom_storage.md similarity index 100% rename from backend/docs/docs/examples/custom_storage.md rename to docs/docs/examples/custom_storage.md diff --git a/backend/docs/docs/examples/index.md b/docs/docs/examples/index.md similarity index 100% rename from backend/docs/docs/examples/index.md rename to docs/docs/examples/index.md diff --git a/backend/docs/docs/index.md b/docs/docs/index.md similarity index 100% rename from backend/docs/docs/index.md rename to docs/docs/index.md diff --git a/backend/docs/docs/installation.md b/docs/docs/installation.md similarity index 100% rename from backend/docs/docs/installation.md rename to docs/docs/installation.md diff --git a/backend/docs/docs/parsers/index.md b/docs/docs/parsers/index.md similarity index 100% rename from backend/docs/docs/parsers/index.md rename to docs/docs/parsers/index.md diff --git a/backend/docs/docs/parsers/megaparse.md b/docs/docs/parsers/megaparse.md similarity index 100% rename from backend/docs/docs/parsers/megaparse.md rename to docs/docs/parsers/megaparse.md diff --git a/backend/docs/docs/parsers/simple.md b/docs/docs/parsers/simple.md similarity index 100% rename from backend/docs/docs/parsers/simple.md rename to docs/docs/parsers/simple.md diff --git a/backend/docs/docs/storage/base.md b/docs/docs/storage/base.md similarity index 100% rename from backend/docs/docs/storage/base.md rename to docs/docs/storage/base.md diff --git a/backend/docs/docs/storage/index.md b/docs/docs/storage/index.md similarity index 100% rename from backend/docs/docs/storage/index.md rename to docs/docs/storage/index.md diff --git a/backend/docs/docs/storage/local_storage.md b/docs/docs/storage/local_storage.md similarity index 100% rename from backend/docs/docs/storage/local_storage.md rename to docs/docs/storage/local_storage.md diff --git a/backend/docs/docs/vectorstores/faiss.md b/docs/docs/vectorstores/faiss.md similarity index 100% rename from backend/docs/docs/vectorstores/faiss.md rename to docs/docs/vectorstores/faiss.md diff --git a/backend/docs/docs/vectorstores/index.md b/docs/docs/vectorstores/index.md similarity index 100% rename from backend/docs/docs/vectorstores/index.md rename to docs/docs/vectorstores/index.md diff --git a/backend/docs/docs/vectorstores/pgvector.md b/docs/docs/vectorstores/pgvector.md similarity index 100% rename from backend/docs/docs/vectorstores/pgvector.md rename to docs/docs/vectorstores/pgvector.md diff --git a/backend/docs/docs/workflows/examples/chat.md b/docs/docs/workflows/examples/chat.md similarity index 100% rename from backend/docs/docs/workflows/examples/chat.md rename to docs/docs/workflows/examples/chat.md diff --git a/backend/docs/docs/workflows/examples/rag_with_internet.md b/docs/docs/workflows/examples/rag_with_internet.md similarity index 100% rename from backend/docs/docs/workflows/examples/rag_with_internet.md rename to docs/docs/workflows/examples/rag_with_internet.md diff --git a/backend/docs/docs/workflows/index.md b/docs/docs/workflows/index.md similarity index 100% rename from backend/docs/docs/workflows/index.md rename to docs/docs/workflows/index.md diff --git a/backend/docs/mkdocs.yml b/docs/mkdocs.yml similarity index 100% rename from backend/docs/mkdocs.yml rename to docs/mkdocs.yml diff --git a/backend/docs/overrides/empty b/docs/overrides/empty similarity index 100% rename from backend/docs/overrides/empty rename to docs/overrides/empty diff --git a/backend/docs/pyproject.toml b/docs/pyproject.toml similarity index 81% rename from backend/docs/pyproject.toml rename to docs/pyproject.toml index 01de36161..068928248 100644 --- a/backend/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -6,6 +6,7 @@ authors = [ { name = "Stan Girard", email = "stan@quivr.app" } ] dependencies = [ + "quivr-core>=0.0.18", "mkdocs>=1.6.1", "mkdocstrings[python]>=0.26.0", "mkdocs-jupyter>=0.24.8", @@ -26,10 +27,6 @@ dev-dependencies = [] virtual = true [tool.rye.scripts] -venv_create = "uv venv ./.venv" -venv_sync= "uv pip sync --python ./.venv/bin/python ./requirements.lock" -venv_create_sync = {chain = ["venv_create", "venv_sync"]} -check_deps = "basedpyright --project ./pyproject.toml" docs = "mkdocs serve" build_docs = "mkdocs build --strict" diff --git a/backend/docs/requirements-dev.lock b/docs/requirements-dev.lock similarity index 56% rename from backend/docs/requirements-dev.lock rename to docs/requirements-dev.lock index 7f2d5362c..2348d21d1 100644 --- a/backend/docs/requirements-dev.lock +++ b/docs/requirements-dev.lock @@ -9,9 +9,26 @@ # generate-hashes: false # universal: false +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 + # via aiohttp +aiohttp==3.10.10 + # via langchain + # via langchain-community +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.36.1 + # via langchain-anthropic +anyio==4.6.2.post1 + # via anthropic + # via httpx asttokens==2.4.1 # via stack-data attrs==24.2.0 + # via aiohttp # via jsonschema # via referencing babel==2.16.0 @@ -21,6 +38,8 @@ beautifulsoup4==4.12.3 bleach==6.1.0 # via nbconvert certifi==2024.8.30 + # via httpcore + # via httpx # via requests charset-normalizer==3.3.2 # via requests @@ -32,22 +51,54 @@ colorama==0.4.6 # via mkdocs-material comm==0.2.2 # via ipykernel +dataclasses-json==0.6.7 + # via langchain-community debugpy==1.8.5 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 + # via langchain-anthropic # via nbconvert +distro==1.9.0 + # via anthropic executing==2.1.0 # via stack-data fastjsonschema==2.20.0 # via nbformat +filelock==3.16.1 + # via huggingface-hub + # via transformers +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +fsspec==2024.9.0 + # via huggingface-hub ghp-import==2.1.0 # via mkdocs +greenlet==3.1.1 + # via sqlalchemy griffe==1.2.0 # via mkdocstrings-python +h11==0.14.0 + # via httpcore +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via anthropic + # via langgraph-sdk + # via langsmith + # via quivr-core +httpx-sse==0.4.0 + # via langgraph-sdk +huggingface-hub==0.25.2 + # via tokenizers + # via transformers idna==3.8 + # via anyio + # via httpx # via requests + # via yarl ipykernel==6.29.5 # via mkdocs-jupyter ipython==8.27.0 @@ -59,6 +110,12 @@ jinja2==3.1.4 # via mkdocs-material # via mkdocstrings # via nbconvert +jiter==0.6.1 + # via anthropic +jsonpatch==1.33 + # via langchain-core +jsonpointer==3.0.0 + # via jsonpatch jsonschema==4.23.0 # via nbformat jsonschema-specifications==2023.12.1 @@ -76,6 +133,33 @@ jupyterlab-pygments==0.3.0 # via nbconvert jupytext==1.16.4 # via mkdocs-jupyter +langchain==0.2.16 + # via langchain-community + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-community==0.2.17 + # via quivr-core +langchain-core==0.2.41 + # via langchain + # via langchain-anthropic + # via langchain-community + # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint + # via quivr-core +langchain-text-splitters==0.2.4 + # via langchain +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 + # via langchain + # via langchain-community + # via langchain-core markdown==3.7 # via mkdocs # via mkdocs-autorefs @@ -85,12 +169,15 @@ markdown==3.7 markdown-it-py==3.0.0 # via jupytext # via mdit-py-plugins + # via rich markupsafe==2.1.5 # via jinja2 # via mkdocs # via mkdocs-autorefs # via mkdocstrings # via nbconvert +marshmallow==3.22.0 + # via dataclasses-json matplotlib-inline==0.1.7 # via ipykernel # via ipython @@ -126,6 +213,13 @@ mkdocstrings==0.26.0 # via mkdocstrings-python mkdocstrings-python==1.10.9 # via mkdocstrings +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 + # via aiohttp + # via yarl +mypy-extensions==1.0.0 + # via typing-inspect nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -136,11 +230,22 @@ nbformat==5.10.4 # via nbconvert nest-asyncio==1.6.0 # via ipykernel +numpy==1.26.4 + # via langchain + # via langchain-community + # via transformers +orjson==3.10.7 + # via langgraph-sdk + # via langsmith packaging==24.1 + # via huggingface-hub # via ipykernel # via jupytext + # via langchain-core + # via marshmallow # via mkdocs # via nbconvert + # via transformers paginate==0.5.7 # via mkdocs-material pandocfilters==1.5.1 @@ -157,17 +262,30 @@ platformdirs==4.2.2 # via mkdocstrings prompt-toolkit==3.0.47 # via ipython +propcache==0.2.0 + # via yarl +protobuf==5.28.2 + # via transformers psutil==6.0.0 # via ipykernel ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data +pydantic==2.9.2 + # via anthropic + # via langchain + # via langchain-core + # via langsmith + # via quivr-core +pydantic-core==2.23.4 + # via pydantic pygments==2.18.0 # via ipython # via mkdocs-jupyter # via mkdocs-material # via nbconvert + # via rich pymdown-extensions==10.9 # via mkdocs-material # via mkdocstrings @@ -175,39 +293,81 @@ python-dateutil==2.9.0.post0 # via ghp-import # via jupyter-client pyyaml==6.0.2 + # via huggingface-hub # via jupytext + # via langchain + # via langchain-community + # via langchain-core # via mkdocs # via mkdocs-get-deps # via pymdown-extensions # via pyyaml-env-tag + # via transformers pyyaml-env-tag==0.1 # via mkdocs pyzmq==26.2.0 # via ipykernel # via jupyter-client +quivr-core==0.0.18 referencing==0.35.1 # via jsonschema # via jsonschema-specifications regex==2024.7.24 # via mkdocs-material + # via tiktoken + # via transformers requests==2.32.3 + # via huggingface-hub + # via langchain + # via langchain-community + # via langsmith # via mkdocs-material + # via requests-toolbelt + # via tiktoken + # via transformers +requests-toolbelt==1.0.0 + # via langsmith +rich==13.9.2 + # via quivr-core rpds-py==0.20.0 # via jsonschema # via referencing +safetensors==0.4.5 + # via transformers +sentencepiece==0.2.0 + # via transformers six==1.16.0 # via asttokens # via bleach # via python-dateutil +sniffio==1.3.1 + # via anthropic + # via anyio + # via httpx soupsieve==2.6 # via beautifulsoup4 +sqlalchemy==2.0.36 + # via langchain + # via langchain-community stack-data==0.6.3 # via ipython +tenacity==8.5.0 + # via langchain + # via langchain-community + # via langchain-core +tiktoken==0.8.0 + # via quivr-core tinycss2==1.3.0 # via nbconvert +tokenizers==0.20.1 + # via anthropic + # via transformers tornado==6.4.1 # via ipykernel # via jupyter-client +tqdm==4.66.5 + # via huggingface-hub + # via transformers traitlets==5.14.3 # via comm # via ipykernel @@ -218,8 +378,21 @@ traitlets==5.14.3 # via nbclient # via nbconvert # via nbformat +transformers==4.45.2 + # via quivr-core +types-pyyaml==6.0.12.20240917 + # via quivr-core typing-extensions==4.12.2 + # via anthropic + # via huggingface-hub # via ipython + # via langchain-core + # via pydantic + # via pydantic-core + # via sqlalchemy + # via typing-inspect +typing-inspect==0.9.0 + # via dataclasses-json urllib3==2.2.2 # via requests watchdog==5.0.0 @@ -229,3 +402,5 @@ wcwidth==0.2.13 webencodings==0.5.1 # via bleach # via tinycss2 +yarl==1.15.4 + # via aiohttp diff --git a/backend/docs/requirements.lock b/docs/requirements.lock similarity index 56% rename from backend/docs/requirements.lock rename to docs/requirements.lock index 7f2d5362c..2348d21d1 100644 --- a/backend/docs/requirements.lock +++ b/docs/requirements.lock @@ -9,9 +9,26 @@ # generate-hashes: false # universal: false +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 + # via aiohttp +aiohttp==3.10.10 + # via langchain + # via langchain-community +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.36.1 + # via langchain-anthropic +anyio==4.6.2.post1 + # via anthropic + # via httpx asttokens==2.4.1 # via stack-data attrs==24.2.0 + # via aiohttp # via jsonschema # via referencing babel==2.16.0 @@ -21,6 +38,8 @@ beautifulsoup4==4.12.3 bleach==6.1.0 # via nbconvert certifi==2024.8.30 + # via httpcore + # via httpx # via requests charset-normalizer==3.3.2 # via requests @@ -32,22 +51,54 @@ colorama==0.4.6 # via mkdocs-material comm==0.2.2 # via ipykernel +dataclasses-json==0.6.7 + # via langchain-community debugpy==1.8.5 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 + # via langchain-anthropic # via nbconvert +distro==1.9.0 + # via anthropic executing==2.1.0 # via stack-data fastjsonschema==2.20.0 # via nbformat +filelock==3.16.1 + # via huggingface-hub + # via transformers +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +fsspec==2024.9.0 + # via huggingface-hub ghp-import==2.1.0 # via mkdocs +greenlet==3.1.1 + # via sqlalchemy griffe==1.2.0 # via mkdocstrings-python +h11==0.14.0 + # via httpcore +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via anthropic + # via langgraph-sdk + # via langsmith + # via quivr-core +httpx-sse==0.4.0 + # via langgraph-sdk +huggingface-hub==0.25.2 + # via tokenizers + # via transformers idna==3.8 + # via anyio + # via httpx # via requests + # via yarl ipykernel==6.29.5 # via mkdocs-jupyter ipython==8.27.0 @@ -59,6 +110,12 @@ jinja2==3.1.4 # via mkdocs-material # via mkdocstrings # via nbconvert +jiter==0.6.1 + # via anthropic +jsonpatch==1.33 + # via langchain-core +jsonpointer==3.0.0 + # via jsonpatch jsonschema==4.23.0 # via nbformat jsonschema-specifications==2023.12.1 @@ -76,6 +133,33 @@ jupyterlab-pygments==0.3.0 # via nbconvert jupytext==1.16.4 # via mkdocs-jupyter +langchain==0.2.16 + # via langchain-community + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-community==0.2.17 + # via quivr-core +langchain-core==0.2.41 + # via langchain + # via langchain-anthropic + # via langchain-community + # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint + # via quivr-core +langchain-text-splitters==0.2.4 + # via langchain +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 + # via langchain + # via langchain-community + # via langchain-core markdown==3.7 # via mkdocs # via mkdocs-autorefs @@ -85,12 +169,15 @@ markdown==3.7 markdown-it-py==3.0.0 # via jupytext # via mdit-py-plugins + # via rich markupsafe==2.1.5 # via jinja2 # via mkdocs # via mkdocs-autorefs # via mkdocstrings # via nbconvert +marshmallow==3.22.0 + # via dataclasses-json matplotlib-inline==0.1.7 # via ipykernel # via ipython @@ -126,6 +213,13 @@ mkdocstrings==0.26.0 # via mkdocstrings-python mkdocstrings-python==1.10.9 # via mkdocstrings +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 + # via aiohttp + # via yarl +mypy-extensions==1.0.0 + # via typing-inspect nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -136,11 +230,22 @@ nbformat==5.10.4 # via nbconvert nest-asyncio==1.6.0 # via ipykernel +numpy==1.26.4 + # via langchain + # via langchain-community + # via transformers +orjson==3.10.7 + # via langgraph-sdk + # via langsmith packaging==24.1 + # via huggingface-hub # via ipykernel # via jupytext + # via langchain-core + # via marshmallow # via mkdocs # via nbconvert + # via transformers paginate==0.5.7 # via mkdocs-material pandocfilters==1.5.1 @@ -157,17 +262,30 @@ platformdirs==4.2.2 # via mkdocstrings prompt-toolkit==3.0.47 # via ipython +propcache==0.2.0 + # via yarl +protobuf==5.28.2 + # via transformers psutil==6.0.0 # via ipykernel ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data +pydantic==2.9.2 + # via anthropic + # via langchain + # via langchain-core + # via langsmith + # via quivr-core +pydantic-core==2.23.4 + # via pydantic pygments==2.18.0 # via ipython # via mkdocs-jupyter # via mkdocs-material # via nbconvert + # via rich pymdown-extensions==10.9 # via mkdocs-material # via mkdocstrings @@ -175,39 +293,81 @@ python-dateutil==2.9.0.post0 # via ghp-import # via jupyter-client pyyaml==6.0.2 + # via huggingface-hub # via jupytext + # via langchain + # via langchain-community + # via langchain-core # via mkdocs # via mkdocs-get-deps # via pymdown-extensions # via pyyaml-env-tag + # via transformers pyyaml-env-tag==0.1 # via mkdocs pyzmq==26.2.0 # via ipykernel # via jupyter-client +quivr-core==0.0.18 referencing==0.35.1 # via jsonschema # via jsonschema-specifications regex==2024.7.24 # via mkdocs-material + # via tiktoken + # via transformers requests==2.32.3 + # via huggingface-hub + # via langchain + # via langchain-community + # via langsmith # via mkdocs-material + # via requests-toolbelt + # via tiktoken + # via transformers +requests-toolbelt==1.0.0 + # via langsmith +rich==13.9.2 + # via quivr-core rpds-py==0.20.0 # via jsonschema # via referencing +safetensors==0.4.5 + # via transformers +sentencepiece==0.2.0 + # via transformers six==1.16.0 # via asttokens # via bleach # via python-dateutil +sniffio==1.3.1 + # via anthropic + # via anyio + # via httpx soupsieve==2.6 # via beautifulsoup4 +sqlalchemy==2.0.36 + # via langchain + # via langchain-community stack-data==0.6.3 # via ipython +tenacity==8.5.0 + # via langchain + # via langchain-community + # via langchain-core +tiktoken==0.8.0 + # via quivr-core tinycss2==1.3.0 # via nbconvert +tokenizers==0.20.1 + # via anthropic + # via transformers tornado==6.4.1 # via ipykernel # via jupyter-client +tqdm==4.66.5 + # via huggingface-hub + # via transformers traitlets==5.14.3 # via comm # via ipykernel @@ -218,8 +378,21 @@ traitlets==5.14.3 # via nbclient # via nbconvert # via nbformat +transformers==4.45.2 + # via quivr-core +types-pyyaml==6.0.12.20240917 + # via quivr-core typing-extensions==4.12.2 + # via anthropic + # via huggingface-hub # via ipython + # via langchain-core + # via pydantic + # via pydantic-core + # via sqlalchemy + # via typing-inspect +typing-inspect==0.9.0 + # via dataclasses-json urllib3==2.2.2 # via requests watchdog==5.0.0 @@ -229,3 +402,5 @@ wcwidth==0.2.13 webencodings==0.5.1 # via bleach # via tinycss2 +yarl==1.15.4 + # via aiohttp diff --git a/backend/docs/src/docs/__init__.py b/docs/src/docs/__init__.py similarity index 100% rename from backend/docs/src/docs/__init__.py rename to docs/src/docs/__init__.py diff --git a/examples/chatbot/.chainlit/config.toml b/examples/chatbot/.chainlit/config.toml new file mode 100644 index 000000000..eb911b503 --- /dev/null +++ b/examples/chatbot/.chainlit/config.toml @@ -0,0 +1,120 @@ +[project] +# Whether to enable telemetry (default: true). No personal data is collected. +enable_telemetry = true + + +# List of environment variables to be provided by each user to use the app. +user_env = [] + +# Duration (in seconds) during which the session is saved when the connection is lost +session_timeout = 3600 + +# Enable third parties caching (e.g LangChain cache) +cache = false + +# Authorized origins +allow_origins = ["*"] + +# Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317) +# follow_symlink = false + +[features] +# Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript) +unsafe_allow_html = false + +# Process and display mathematical expressions. This can clash with "$" characters in messages. +latex = false + +# Automatically tag threads with the current chat profile (if a chat profile is used) +auto_tag_thread = true + +# Authorize users to spontaneously upload files with messages +[features.spontaneous_file_upload] + enabled = true + accept = ["*/*"] + max_files = 20 + max_size_mb = 500 + +[features.audio] + # Threshold for audio recording + min_decibels = -45 + # Delay for the user to start speaking in MS + initial_silence_timeout = 3000 + # Delay for the user to continue speaking in MS. If the user stops speaking for this duration, the recording will stop. + silence_timeout = 1500 + # Above this duration (MS), the recording will forcefully stop. + max_duration = 15000 + # Duration of the audio chunks in MS + chunk_duration = 1000 + # Sample rate of the audio + sample_rate = 44100 + +edit_message = true + +[UI] +# Name of the assistant. +name = "Assistant" + +# Description of the assistant. This is used for HTML tags. +# description = "" + +# Large size content are by default collapsed for a cleaner ui +default_collapse_content = true + +# Chain of Thought (CoT) display mode. Can be "hidden", "tool_call" or "full". +cot = "full" + +# Link to your github repo. This will add a github button in the UI's header. +# github = "" + +# Specify a CSS file that can be used to customize the user interface. +# The CSS file can be served from the public directory or via an external link. +# custom_css = "/public/test.css" + +# Specify a Javascript file that can be used to customize the user interface. +# The Javascript file can be served from the public directory. +# custom_js = "/public/test.js" + +# Specify a custom font url. +# custom_font = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" + +# Specify a custom meta image url. +# custom_meta_image_url = "https://chainlit-cloud.s3.eu-west-3.amazonaws.com/logo/chainlit_banner.png" + +# Specify a custom build directory for the frontend. +# This can be used to customize the frontend code. +# Be careful: If this is a relative path, it should not start with a slash. +# custom_build = "./public/build" + +[UI.theme] + default = "dark" + #layout = "wide" + #font_family = "Inter, sans-serif" +# Override default MUI light theme. (Check theme.ts) +[UI.theme.light] + #background = "#FAFAFA" + #paper = "#FFFFFF" + + [UI.theme.light.primary] + #main = "#F80061" + #dark = "#980039" + #light = "#FFE7EB" + [UI.theme.light.text] + #primary = "#212121" + #secondary = "#616161" + +# Override default MUI dark theme. (Check theme.ts) +[UI.theme.dark] + #background = "#FAFAFA" + #paper = "#FFFFFF" + + [UI.theme.dark.primary] + #main = "#F80061" + #dark = "#980039" + #light = "#FFE7EB" + [UI.theme.dark.text] + #primary = "#EEEEEE" + #secondary = "#BDBDBD" + +[meta] +generated_by = "1.1.402" diff --git a/examples/chatbot/.chainlit/translations/bn.json b/examples/chatbot/.chainlit/translations/bn.json new file mode 100644 index 000000000..e6bbda38b --- /dev/null +++ b/examples/chatbot/.chainlit/translations/bn.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u09b8\u09c7\u099f\u09bf\u0982\u09b8", + "settingsKey": "S", + "APIKeys": "\u098f\u09aa\u09bf\u0986\u0987 \u0995\u09c0", + "logout": "\u09b2\u0997\u0986\u0989\u099f" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u09a8\u09a4\u09c1\u09a8 \u0986\u09a1\u09cd\u09a1\u09be" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0995\u09be\u09b0\u09cd\u09af \u09a4\u09be\u09b2\u09bf\u0995\u09be", + "loading": "\u09b2\u09cb\u09a1\u0964\u0964\u0964", + "error": "\u098f\u0995\u099f\u09bf \u09a4\u09cd\u09b0\u09c1\u099f\u09bf \u09b8\u0982\u0998\u099f\u09bf\u09a4 \u09b9\u09af\u09bc\u09c7\u099b\u09c7" + } + }, + "attachments": { + "cancelUpload": "\u0986\u09aa\u09b2\u09cb\u09a1 \u09ac\u09be\u09a4\u09bf\u09b2 \u0995\u09b0\u09c1\u09a8", + "removeAttachment": "\u09b8\u0982\u09af\u09c1\u0995\u09cd\u09a4\u09bf \u09b8\u09b0\u09be\u09a8" + }, + "newChatDialog": { + "createNewChat": "\u09a8\u09a4\u09c1\u09a8 \u099a\u09cd\u09af\u09be\u099f \u09a4\u09c8\u09b0\u09bf \u0995\u09b0\u09ac\u09c7\u09a8?", + "clearChat": "\u098f\u099f\u09bf \u09ac\u09b0\u09cd\u09a4\u09ae\u09be\u09a8 \u09ac\u09be\u09b0\u09cd\u09a4\u09be\u0997\u09c1\u09b2\u09bf \u09b8\u09be\u09ab \u0995\u09b0\u09ac\u09c7 \u098f\u09ac\u0982 \u098f\u0995\u099f\u09bf \u09a8\u09a4\u09c1\u09a8 \u099a\u09cd\u09af\u09be\u099f \u09b6\u09c1\u09b0\u09c1 \u0995\u09b0\u09ac\u09c7\u0964", + "cancel": "\u09ac\u09be\u09a4\u09bf\u09b2", + "confirm": "\u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4" + }, + "settingsModal": { + "settings": "\u09b8\u09c7\u099f\u09bf\u0982\u09b8", + "expandMessages": "\u09ac\u09be\u09b0\u09cd\u09a4\u09be\u0997\u09c1\u09b2\u09bf \u09aa\u09cd\u09b0\u09b8\u09be\u09b0\u09bf\u09a4 \u0995\u09b0\u09c1\u09a8", + "hideChainOfThought": "\u099a\u09bf\u09a8\u09cd\u09a4\u09be\u09b0 \u09b6\u09c3\u0999\u09cd\u0996\u09b2 \u09b2\u09c1\u0995\u09be\u09a8", + "darkMode": "\u09a1\u09be\u09b0\u09cd\u0995 \u09ae\u09cb\u09a1" + }, + "detailsButton": { + "using": "\u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0", + "running": "\u099a\u09b2\u09ae\u09be\u09a8", + "took_one": "{{count}} \u09aa\u09a6\u0995\u09cd\u09b7\u09c7\u09aa \u09a8\u09bf\u09af\u09bc\u09c7\u099b\u09c7", + "took_other": "{{count}}\u099f\u09bf \u09aa\u09a6\u0995\u09cd\u09b7\u09c7\u09aa \u09a8\u09bf\u09af\u09bc\u09c7\u099b\u09c7" + }, + "auth": { + "authLogin": { + "title": "\u0985\u09cd\u09af\u09be\u09aa\u099f\u09bf \u0985\u09cd\u09af\u09be\u0995\u09cd\u09b8\u09c7\u09b8 \u0995\u09b0\u09a4\u09c7 \u09b2\u0997\u0987\u09a8 \u0995\u09b0\u09c1\u09a8\u0964", + "form": { + "email": "\u0987-\u09ae\u09c7\u0987\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be", + "password": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1", + "noAccount": "\u0995\u09cb\u09a8\u0993 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a8\u09c7\u0987?", + "alreadyHaveAccount": "\u0987\u09a4\u09bf\u09ae\u09a7\u09cd\u09af\u09c7 \u098f\u0995\u099f\u09bf \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u0986\u099b\u09c7?", + "signup": "\u09b8\u09be\u0987\u09a8 \u0986\u09aa \u0995\u09b0\u09cb", + "signin": "\u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09cb", + "or": "\u09ac\u09be", + "continue": "\u0985\u09ac\u09bf\u09b0\u09a4", + "forgotPassword": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09ad\u09c1\u09b2\u09c7 \u0997\u09c7\u099b\u09c7\u09a8?", + "passwordMustContain": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1\u09c7 \u0985\u09ac\u09b6\u09cd\u09af\u0987 \u09a5\u09be\u0995\u09a4\u09c7 \u09b9\u09ac\u09c7:", + "emailRequired": "\u0987\u09ae\u09c7\u09b2 \u098f\u0995\u099f\u09bf \u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u099c\u09a8\u09c0\u09af\u09bc \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0", + "passwordRequired": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u098f\u0995\u099f\u09bf \u0986\u09ac\u09b6\u09cd\u09af\u0995 \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0" + }, + "error": { + "default": "\u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09a4\u09c7 \u0985\u0995\u09cd\u09b7\u09ae\u0964", + "signin": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "oauthsignin": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "redirect_uri_mismatch": "\u09aa\u09c1\u09a8\u0983\u09a8\u09bf\u09b0\u09cd\u09a6\u09c7\u09b6\u09bf\u09a4 URI OAUTH \u0985\u09cd\u09af\u09be\u09aa \u0995\u09a8\u09ab\u09bf\u0997\u09be\u09b0\u09c7\u09b6\u09a8\u09c7\u09b0 \u09b8\u09be\u09a5\u09c7 \u09ae\u09bf\u09b2\u099b\u09c7 \u09a8\u09be\u0964", + "oauthcallbackerror": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "oauthcreateaccount": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "emailcreateaccount": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "callback": "\u098f\u0995\u099f\u09bf \u09ad\u09bf\u09a8\u09cd\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "oauthaccountnotlinked": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09b0\u09bf\u099a\u09af\u09bc \u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4 \u0995\u09b0\u09a4\u09c7, \u0986\u09aa\u09a8\u09bf \u09ae\u09c2\u09b2\u09a4 \u09af\u09c7 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f\u099f\u09bf \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0995\u09b0\u09c7\u099b\u09c7\u09a8 \u09b8\u09c7\u0987 \u098f\u0995\u0987 \u0985\u09cd\u09af\u09be\u0995\u09be\u0989\u09a8\u09cd\u099f \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09c1\u09a8\u0964", + "emailsignin": "\u0987-\u09ae\u09c7\u0987\u09b2\u099f\u09bf \u09aa\u09cd\u09b0\u09c7\u09b0\u09a3 \u0995\u09b0\u09be \u09af\u09be\u09af\u09bc\u09a8\u09bf\u0964", + "emailverify": "\u0985\u09a8\u09c1\u0997\u09cd\u09b0\u09b9 \u0995\u09b0\u09c7 \u0986\u09aa\u09a8\u09be\u09b0 \u0987\u09ae\u09c7\u09b2\u099f\u09bf \u09af\u09be\u099a\u09be\u0987 \u0995\u09b0\u09c1\u09a8, \u098f\u0995\u099f\u09bf \u09a8\u09a4\u09c1\u09a8 \u0987\u09ae\u09c7\u09b2 \u09aa\u09cd\u09b0\u09c7\u09b0\u09a3 \u0995\u09b0\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964", + "credentialssignin": "\u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u09ac\u09cd\u09af\u09b0\u09cd\u09a5 \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964 \u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09cd\u09b0\u09a6\u09a4\u09cd\u09a4 \u09ac\u09bf\u09ac\u09b0\u09a3\u0997\u09c1\u09b2\u09bf \u09b8\u09a0\u09bf\u0995 \u0995\u09bf\u09a8\u09be \u09a4\u09be \u09aa\u09b0\u09c0\u0995\u09cd\u09b7\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "sessionrequired": "\u098f\u0987 \u09aa\u09c3\u09b7\u09cd\u09a0\u09be\u099f\u09bf \u0985\u09cd\u09af\u09be\u0995\u09cd\u09b8\u09c7\u09b8 \u0995\u09b0\u09a4\u09c7 \u09a6\u09af\u09bc\u09be \u0995\u09b0\u09c7 \u09b8\u09be\u0987\u09a8 \u0987\u09a8 \u0995\u09b0\u09c1\u09a8\u0964" + } + }, + "authVerifyEmail": { + "almostThere": "\u0986\u09aa\u09a8\u09bf \u09aa\u09cd\u09b0\u09be\u09af\u09bc \u09b8\u09c7\u0996\u09be\u09a8\u09c7 \u09aa\u09cc\u0981\u099b\u09c7\u099b\u09c7\u09a8! \u0986\u09ae\u09b0\u09be \u098f\u0995\u099f\u09bf \u0987\u09ae\u09c7\u0987\u09b2 \u09aa\u09be\u09a0\u09bf\u09af\u09bc\u09c7\u099b\u09bf ", + "verifyEmailLink": "\u0986\u09aa\u09a8\u09be\u09b0 \u09b8\u09be\u0987\u09a8\u0986\u09aa \u09b8\u09ae\u09cd\u09aa\u09c2\u09b0\u09cd\u09a3 \u0995\u09b0\u09a4\u09c7 \u09a6\u09af\u09bc\u09be \u0995\u09b0\u09c7 \u09b8\u09c7\u0987 \u0987\u09ae\u09c7\u09b2\u09c7\u09b0 \u09b2\u09bf\u0999\u09cd\u0995\u099f\u09bf\u09a4\u09c7 \u0995\u09cd\u09b2\u09bf\u0995 \u0995\u09b0\u09c1\u09a8\u0964", + "didNotReceive": "\u0987\u09ae\u09c7\u0987\u09b2 \u0996\u09c1\u0981\u099c\u09c7 \u09aa\u09be\u099a\u09cd\u099b\u09c7\u09a8 \u09a8\u09be?", + "resendEmail": "\u0987\u09ae\u09c7\u0987\u09b2 \u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09aa\u09be\u09a0\u09be\u09a8", + "goBack": "\u09ab\u09bf\u09b0\u09c7 \u09af\u09be\u0993", + "emailSent": "\u0987\u09ae\u09c7\u09b2 \u09b8\u09ab\u09b2\u09ad\u09be\u09ac\u09c7 \u09aa\u09be\u09a0\u09be\u09a8\u09cb \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964", + "verifyEmail": "\u0986\u09aa\u09a8\u09be\u09b0 \u0987\u09ae\u09c7\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be \u09af\u09be\u099a\u09be\u0987 \u0995\u09b0\u09c1\u09a8" + }, + "providerButton": { + "continue": "{{provider}} \u09a6\u09bf\u09af\u09bc\u09c7 \u099a\u09be\u09b2\u09bf\u09af\u09bc\u09c7 \u09af\u09be\u09a8", + "signup": "{{provider}} \u09a6\u09bf\u09af\u09bc\u09c7 \u09b8\u09be\u0987\u09a8 \u0986\u09aa \u0995\u09b0\u09c1\u09a8" + }, + "authResetPassword": { + "newPasswordRequired": "\u09a8\u09a4\u09c1\u09a8 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u098f\u0995\u099f\u09bf \u0986\u09ac\u09b6\u09cd\u09af\u0995 \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0", + "passwordsMustMatch": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u0985\u09ac\u09b6\u09cd\u09af\u0987 \u09ae\u09bf\u09b2\u09a4\u09c7 \u09b9\u09ac\u09c7", + "confirmPasswordRequired": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4 \u0995\u09b0\u09be \u098f\u0995\u099f\u09bf \u0986\u09ac\u09b6\u09cd\u09af\u0995 \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0", + "newPassword": "\u09a8\u09a4\u09c1\u09a8 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1", + "confirmPassword": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4 \u0995\u09b0\u09c1\u09a8", + "resetPassword": "\u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09b0\u09bf\u09b8\u09c7\u099f \u0995\u09b0\u09c1\u09a8" + }, + "authForgotPassword": { + "email": "\u0987-\u09ae\u09c7\u0987\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be", + "emailRequired": "\u0987\u09ae\u09c7\u09b2 \u098f\u0995\u099f\u09bf \u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u099c\u09a8\u09c0\u09af\u09bc \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0", + "emailSent": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1\u099f\u09bf \u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09b8\u09c7\u099f \u0995\u09b0\u09be\u09b0 \u09a8\u09bf\u09b0\u09cd\u09a6\u09c7\u09b6\u09be\u09ac\u09b2\u09c0\u09b0 \u099c\u09a8\u09cd\u09af \u09a6\u09af\u09bc\u09be \u0995\u09b0\u09c7 \u0987\u09ae\u09c7\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be {{email}} \u09aa\u09b0\u09c0\u0995\u09cd\u09b7\u09be \u0995\u09b0\u09c1\u09a8\u0964", + "enterEmail": "\u0986\u09aa\u09a8\u09be\u09b0 \u0987\u09ae\u09c7\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be \u09b2\u09bf\u0996\u09c1\u09a8 \u098f\u09ac\u0982 \u0986\u09ae\u09b0\u09be \u0986\u09aa\u09a8\u09be\u0995\u09c7 \u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09b8\u09c7\u099f \u0995\u09b0\u09a4\u09c7 \u09a8\u09bf\u09b0\u09cd\u09a6\u09c7\u09b6\u09be\u09ac\u09b2\u09c0 \u09aa\u09be\u09a0\u09be\u09ac\u0964", + "resendEmail": "\u0987\u09ae\u09c7\u0987\u09b2 \u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09aa\u09be\u09a0\u09be\u09a8", + "continue": "\u0985\u09ac\u09bf\u09b0\u09a4", + "goBack": "\u09ab\u09bf\u09b0\u09c7 \u09af\u09be\u0993" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0987\u09a4\u09bf\u09b9\u09be\u09b8 \u09a6\u09c7\u0996\u09be\u09a8", + "lastInputs": "\u09b8\u09b0\u09cd\u09ac\u09b6\u09c7\u09b7 \u0987\u09a8\u09aa\u09c1\u099f", + "noInputs": "\u098f\u09a4 \u09ab\u09be\u0981\u0995\u09be...", + "loading": "\u09b2\u09cb\u09a1\u0964\u0964\u0964" + } + }, + "inputBox": { + "input": { + "placeholder": "\u098f\u0996\u09be\u09a8\u09c7 \u0986\u09aa\u09a8\u09be\u09b0 \u09ac\u09be\u09b0\u09cd\u09a4\u09be \u099f\u09be\u0987\u09aa \u0995\u09b0\u09c1\u09a8..." + }, + "speechButton": { + "start": "\u09b0\u09c7\u0995\u09b0\u09cd\u09a1\u09bf\u0982 \u09b6\u09c1\u09b0\u09c1 \u0995\u09b0\u09c1\u09a8", + "stop": "\u09b0\u09c7\u0995\u09b0\u09cd\u09a1\u09bf\u0982 \u09ac\u09a8\u09cd\u09a7 \u0995\u09b0\u09c1\u09a8" + }, + "SubmitButton": { + "sendMessage": "\u09ac\u09be\u09b0\u09cd\u09a4\u09be \u09aa\u09cd\u09b0\u09c7\u09b0\u09a3 \u0995\u09b0\u09c1\u09a8", + "stopTask": "\u09b8\u09cd\u099f\u09aa \u099f\u09be\u09b8\u09cd\u0995" + }, + "UploadButton": { + "attachFiles": "\u09ab\u09be\u0987\u09b2 \u09b8\u0982\u09af\u09c1\u0995\u09cd\u09a4 \u0995\u09b0\u09c1\u09a8" + }, + "waterMark": { + "text": "\u09b8\u0999\u09cd\u0997\u09c7 \u09a8\u09bf\u09b0\u09cd\u09ae\u09bf\u09a4" + } + }, + "Messages": { + "index": { + "running": "\u099a\u09b2\u09ae\u09be\u09a8", + "executedSuccessfully": "\u09b8\u09ab\u09b2\u09ad\u09be\u09ac\u09c7 \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09bf\u09a4 \u09b9\u09af\u09bc\u09c7\u099b\u09c7", + "failed": "\u09ac\u09cd\u09af\u09b0\u09cd\u09a5", + "feedbackUpdated": "\u09ab\u09bf\u09a1\u09ac\u09cd\u09af\u09be\u0995 \u0986\u09aa\u09a1\u09c7\u099f \u09b9\u09af\u09bc\u09c7\u099b\u09c7", + "updating": "\u0986\u09a7\u09c1\u09a8\u09bf\u0995\u09c0\u0995\u09b0\u09a3" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0986\u09aa\u09a8\u09be\u09b0 \u09ab\u09be\u0987\u09b2\u0997\u09c1\u09b2\u09bf \u098f\u0996\u09be\u09a8\u09c7 \u09ab\u09c7\u09b2\u09c7 \u09a6\u09bf\u09a8" + }, + "index": { + "failedToUpload": "\u0986\u09aa\u09b2\u09cb\u09a1 \u0995\u09b0\u09a4\u09c7 \u09ac\u09cd\u09af\u09b0\u09cd\u09a5 \u09b9\u09af\u09bc\u09c7\u099b\u09c7", + "cancelledUploadOf": "\u098f\u09b0 \u0986\u09aa\u09b2\u09cb\u09a1 \u09ac\u09be\u09a4\u09bf\u09b2", + "couldNotReachServer": "\u09b8\u09be\u09b0\u09cd\u09ad\u09be\u09b0\u09c7 \u09aa\u09cc\u0981\u099b\u09be\u09a8\u09cb \u09af\u09be\u09af\u09bc\u09a8\u09bf", + "continuingChat": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09ac\u09b0\u09cd\u09a4\u09c0 \u099a\u09cd\u09af\u09be\u099f \u0985\u09ac\u09bf\u09b0\u09a4 \u09b0\u09be\u0996\u09be" + }, + "settings": { + "settingsPanel": "\u09b8\u09c7\u099f\u09bf\u0982\u09b8 \u09aa\u09cd\u09af\u09be\u09a8\u09c7\u09b2", + "reset": "\u09b0\u09bf\u09b8\u09c7\u099f", + "cancel": "\u09ac\u09be\u09a4\u09bf\u09b2", + "confirm": "\u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u09aa\u09cd\u09b0\u09a4\u09bf\u0995\u09cd\u09b0\u09bf\u09af\u09bc\u09be: \u09b8\u09ac", + "feedbackPositive": "\u09aa\u09cd\u09b0\u09a4\u09bf\u0995\u09cd\u09b0\u09bf\u09af\u09bc\u09be: \u0987\u09a4\u09bf\u09ac\u09be\u099a\u0995", + "feedbackNegative": "\u09aa\u09cd\u09b0\u09a4\u09bf\u0995\u09cd\u09b0\u09bf\u09af\u09bc\u09be: \u09a8\u09c7\u09a4\u09bf\u09ac\u09be\u099a\u0995" + }, + "SearchBar": { + "search": "\u09b8\u09a8\u09cd\u09a7\u09be\u09a8" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u098f\u099f\u09bf \u09a5\u09cd\u09b0\u09c7\u09a1\u09c7\u09b0 \u09aa\u09be\u09b6\u09be\u09aa\u09be\u09b6\u09bf \u098f\u09b0 \u09ac\u09be\u09b0\u09cd\u09a4\u09be \u098f\u09ac\u0982 \u0989\u09aa\u09be\u09a6\u09be\u09a8\u0997\u09c1\u09b2\u09bf\u0993 \u09ae\u09c1\u099b\u09c7 \u09ab\u09c7\u09b2\u09ac\u09c7\u0964", + "cancel": "\u09ac\u09be\u09a4\u09bf\u09b2", + "confirm": "\u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4", + "deletingChat": "\u099a\u09cd\u09af\u09be\u099f \u09ae\u09cb\u099b\u09be \u09b9\u099a\u09cd\u099b\u09c7", + "chatDeleted": "\u099a\u09cd\u09af\u09be\u099f \u09ae\u09cb\u099b\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09c7" + }, + "index": { + "pastChats": "\u0985\u09a4\u09c0\u09a4 \u099a\u09cd\u09af\u09be\u099f" + }, + "ThreadList": { + "empty": "\u0996\u09be\u09b2\u09bf\u0964\u0964\u0964", + "today": "\u0986\u099c", + "yesterday": "\u0997\u09a4\u0995\u09be\u09b2", + "previous7days": "Previous 7 \u09a6\u09bf\u09a8", + "previous30days": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09ac\u09b0\u09cd\u09a4\u09c0 30 \u09a6\u09bf\u09a8" + }, + "TriggerButton": { + "closeSidebar": "\u09b8\u09be\u0987\u09a1\u09ac\u09be\u09b0 \u09ac\u09a8\u09cd\u09a7 \u0995\u09b0\u09c1\u09a8", + "openSidebar": "\u09b8\u09be\u0987\u09a1\u09ac\u09be\u09b0 \u0996\u09c1\u09b2\u09c1\u09a8" + } + }, + "Thread": { + "backToChat": "\u099a\u09cd\u09af\u09be\u099f\u09c7 \u09ab\u09bf\u09b0\u09c7 \u09af\u09be\u09a8", + "chatCreatedOn": "\u098f\u0987 \u099a\u09cd\u09af\u09be\u099f\u099f\u09bf \u09a4\u09c8\u09b0\u09bf \u0995\u09b0\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09bf\u09b2" + } + }, + "header": { + "chat": "\u0986\u09b2\u09be\u09aa", + "readme": "\u09b0\u09bf\u09a1\u09ae\u09bf" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u09b8\u09b0\u09ac\u09b0\u09be\u09b9\u0995\u09be\u09b0\u09c0\u09a6\u09c7\u09b0 \u0986\u09a8\u09a4\u09c7 \u09ac\u09cd\u09af\u09b0\u09cd\u09a5:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u09b8\u09ab\u09b2\u09ad\u09be\u09ac\u09c7 \u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09a3 \u0995\u09b0\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09c7", + "requiredApiKeys": "\u0986\u09ac\u09b6\u09cd\u09af\u0995 API \u0995\u09c0", + "requiredApiKeysInfo": "\u098f\u0987 \u0985\u09cd\u09af\u09be\u09aa\u099f\u09bf \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0995\u09b0\u09a4\u09c7, \u09a8\u09bf\u09ae\u09cd\u09a8\u09b2\u09bf\u0996\u09bf\u09a4 API \u0995\u09c0\u0997\u09c1\u09b2\u09bf\u09b0 \u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u099c\u09a8\u0964 \u0995\u09c0\u0997\u09c1\u09b2\u09bf \u0986\u09aa\u09a8\u09be\u09b0 \u09a1\u09bf\u09ad\u09be\u0987\u09b8\u09c7\u09b0 \u09b8\u09cd\u09a5\u09be\u09a8\u09c0\u09af\u09bc \u09b8\u09cd\u099f\u09cb\u09b0\u09c7\u099c\u09c7 \u09b8\u099e\u09cd\u099a\u09bf\u09a4 \u09b0\u09af\u09bc\u09c7\u099b\u09c7\u0964" + }, + "Page": { + "notPartOfProject": "\u0986\u09aa\u09a8\u09bf \u098f\u0987 \u09aa\u09cd\u09b0\u0995\u09b2\u09cd\u09aa\u09c7\u09b0 \u0985\u0982\u09b6 \u09a8\u09a8\u0964" + }, + "ResumeButton": { + "resumeChat": "\u099a\u09cd\u09af\u09be\u099f \u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09b6\u09c1\u09b0\u09c1 \u0995\u09b0\u09c1\u09a8" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/en-US.json b/examples/chatbot/.chainlit/translations/en-US.json new file mode 100644 index 000000000..d6d3fea18 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/en-US.json @@ -0,0 +1,229 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "Settings", + "settingsKey": "S", + "APIKeys": "API Keys", + "logout": "Logout" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "New Chat" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f Task List", + "loading": "Loading...", + "error": "An error occurred" + } + }, + "attachments": { + "cancelUpload": "Cancel upload", + "removeAttachment": "Remove attachment" + }, + "newChatDialog": { + "createNewChat": "Create new chat?", + "clearChat": "This will clear the current messages and start a new chat.", + "cancel": "Cancel", + "confirm": "Confirm" + }, + "settingsModal": { + "settings": "Settings", + "expandMessages": "Expand Messages", + "hideChainOfThought": "Hide Chain of Thought", + "darkMode": "Dark Mode" + }, + "detailsButton": { + "using": "Using", + "used": "Used" + }, + "auth": { + "authLogin": { + "title": "Login to access the app.", + "form": { + "email": "Email address", + "password": "Password", + "noAccount": "Don't have an account?", + "alreadyHaveAccount": "Already have an account?", + "signup": "Sign Up", + "signin": "Sign In", + "or": "OR", + "continue": "Continue", + "forgotPassword": "Forgot password?", + "passwordMustContain": "Your password must contain:", + "emailRequired": "email is a required field", + "passwordRequired": "password is a required field" + }, + "error": { + "default": "Unable to sign in.", + "signin": "Try signing in with a different account.", + "oauthsignin": "Try signing in with a different account.", + "redirect_uri_mismatch": "The redirect URI is not matching the oauth app configuration.", + "oauthcallbackerror": "Try signing in with a different account.", + "oauthcreateaccount": "Try signing in with a different account.", + "emailcreateaccount": "Try signing in with a different account.", + "callback": "Try signing in with a different account.", + "oauthaccountnotlinked": "To confirm your identity, sign in with the same account you used originally.", + "emailsignin": "The e-mail could not be sent.", + "emailverify": "Please verify your email, a new email has been sent.", + "credentialssignin": "Sign in failed. Check the details you provided are correct.", + "sessionrequired": "Please sign in to access this page." + } + }, + "authVerifyEmail": { + "almostThere": "You're almost there! We've sent an email to ", + "verifyEmailLink": "Please click on the link in that email to complete your signup.", + "didNotReceive": "Can't find the email?", + "resendEmail": "Resend email", + "goBack": "Go Back", + "emailSent": "Email sent successfully.", + "verifyEmail": "Verify your email address" + }, + "providerButton": { + "continue": "Continue with {{provider}}", + "signup": "Sign up with {{provider}}" + }, + "authResetPassword": { + "newPasswordRequired": "New password is a required field", + "passwordsMustMatch": "Passwords must match", + "confirmPasswordRequired": "Confirm password is a required field", + "newPassword": "New password", + "confirmPassword": "Confirm password", + "resetPassword": "Reset Password" + }, + "authForgotPassword": { + "email": "Email address", + "emailRequired": "email is a required field", + "emailSent": "Please check the email address {{email}} for instructions to reset your password.", + "enterEmail": "Enter your email address and we will send you instructions to reset your password.", + "resendEmail": "Resend email", + "continue": "Continue", + "goBack": "Go Back" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "Show history", + "lastInputs": "Last Inputs", + "noInputs": "Such empty...", + "loading": "Loading..." + } + }, + "inputBox": { + "input": { + "placeholder": "Type your message here..." + }, + "speechButton": { + "start": "Start recording", + "stop": "Stop recording" + }, + "SubmitButton": { + "sendMessage": "Send message", + "stopTask": "Stop Task" + }, + "UploadButton": { + "attachFiles": "Attach files" + }, + "waterMark": { + "text": "Built with" + } + }, + "Messages": { + "index": { + "running": "Running", + "executedSuccessfully": "executed successfully", + "failed": "failed", + "feedbackUpdated": "Feedback updated", + "updating": "Updating" + } + }, + "dropScreen": { + "dropYourFilesHere": "Drop your files here" + }, + "index": { + "failedToUpload": "Failed to upload", + "cancelledUploadOf": "Cancelled upload of", + "couldNotReachServer": "Could not reach the server", + "continuingChat": "Continuing previous chat" + }, + "settings": { + "settingsPanel": "Settings panel", + "reset": "Reset", + "cancel": "Cancel", + "confirm": "Confirm" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "Feedback: All", + "feedbackPositive": "Feedback: Positive", + "feedbackNegative": "Feedback: Negative" + }, + "SearchBar": { + "search": "Search" + } + }, + "DeleteThreadButton": { + "confirmMessage": "This will delete the thread as well as it's messages and elements.", + "cancel": "Cancel", + "confirm": "Confirm", + "deletingChat": "Deleting chat", + "chatDeleted": "Chat deleted" + }, + "index": { + "pastChats": "Past Chats" + }, + "ThreadList": { + "empty": "Empty...", + "today": "Today", + "yesterday": "Yesterday", + "previous7days": "Previous 7 days", + "previous30days": "Previous 30 days" + }, + "TriggerButton": { + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar" + } + }, + "Thread": { + "backToChat": "Go back to chat", + "chatCreatedOn": "This chat was created on" + } + }, + "header": { + "chat": "Chat", + "readme": "Readme" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "Failed to fetch providers:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "Saved successfully", + "requiredApiKeys": "Required API Keys", + "requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage." + }, + "Page": { + "notPartOfProject": "You are not part of this project." + }, + "ResumeButton": { + "resumeChat": "Resume Chat" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/gu.json b/examples/chatbot/.chainlit/translations/gu.json new file mode 100644 index 000000000..561eb9643 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/gu.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0ab8\u0ac1\u0aaf\u0acb\u0a9c\u0aa8\u0acb", + "settingsKey": "S", + "APIKeys": "API \u0a95\u0ac0\u0a93", + "logout": "\u0aac\u0ab9\u0abe\u0ab0 \u0aa8\u0ac0\u0a95\u0ab3\u0acb" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0aa8\u0ab5\u0acb \u0ab8\u0a82\u0ab5\u0abe\u0aa6" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0a95\u0abe\u0ab0\u0acd\u0aaf \u0aaf\u0abe\u0aa6\u0ac0", + "loading": "\u0ab2\u0acb\u0aa1 \u0a95\u0ab0\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac7...", + "error": "\u0aad\u0ac2\u0ab2 \u0a89\u0aa6\u0acd\u0aad\u0ab5\u0ac0" + } + }, + "attachments": { + "cancelUpload": "\u0a85\u0aaa\u0ab2\u0acb\u0aa1 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0ac1\u0a82 \u0ab0\u0aa6 \u0a95\u0ab0\u0acb", + "removeAttachment": "\u0a9c\u0acb\u0aa1\u0abe\u0aa3\u0aa8\u0ac7 \u0aa6\u0ac2\u0ab0 \u0a95\u0ab0\u0acb" + }, + "newChatDialog": { + "createNewChat": "\u0ab6\u0ac1\u0a82 \u0aa8\u0ab5\u0ac1\u0a82 \u0ab8\u0a82\u0ab5\u0abe\u0aa6 \u0aac\u0aa8\u0abe\u0ab5\u0ab5\u0ac1\u0a82 \u0a9b\u0ac7?", + "clearChat": "\u0a86 \u0ab5\u0ab0\u0acd\u0aa4\u0aae\u0abe\u0aa8 \u0ab8\u0a82\u0aa6\u0ac7\u0ab6\u0abe\u0a93\u0aa8\u0ac7 \u0ab8\u0abe\u0aab \u0a95\u0ab0\u0ab6\u0ac7 \u0a85\u0aa8\u0ac7 \u0aa8\u0ab5\u0ac0 \u0ab5\u0abe\u0aa4\u0a9a\u0ac0\u0aa4 \u0ab6\u0ab0\u0ac2 \u0a95\u0ab0\u0ab6\u0ac7.", + "cancel": "\u0ab0\u0aa6\u0acd\u0aa6", + "confirm": "\u0a96\u0abe\u0aa4\u0ab0\u0ac0 \u0a95\u0ab0\u0acb" + }, + "settingsModal": { + "settings": "\u0ab8\u0ac1\u0aaf\u0acb\u0a9c\u0aa8\u0acb", + "expandMessages": "\u0ab8\u0a82\u0aa6\u0ac7\u0ab6\u0abe\u0a93 \u0ab5\u0abf\u0ab8\u0acd\u0aa4\u0ac3\u0aa4 \u0a95\u0ab0\u0acb", + "hideChainOfThought": "\u0ab5\u0abf\u0a9a\u0abe\u0ab0\u0aa8\u0ac0 \u0ab8\u0abe\u0a82\u0a95\u0ab3 \u0a9b\u0ac1\u0aaa\u0abe\u0ab5\u0acb", + "darkMode": "\u0a98\u0abe\u0a9f\u0ac0 \u0ab8\u0acd\u0aa5\u0abf\u0aa4\u0abf" + }, + "detailsButton": { + "using": "\u0ab5\u0abe\u0aaa\u0ab0\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac0\u0a8f", + "running": "\u0a9a\u0abe\u0ab2\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0ac1 \u0a9b\u0ac7", + "took_one": "{{count}} \u0aaa\u0a97\u0ab2\u0ac1\u0a82 \u0aad\u0ab0\u0acd\u0aaf\u0ac1\u0a82", + "took_other": "{{count}} \u0aaa\u0a97\u0ab2\u0abe\u0a82\u0a93 \u0ab2\u0ac0\u0aa7\u0abe" + }, + "auth": { + "authLogin": { + "title": "\u0a8f\u0aaa\u0acd\u0ab2\u0abf\u0a95\u0ac7\u0ab6\u0aa8\u0aa8\u0ac7 \u0a8d\u0a95\u0acd\u0ab8\u0ac7\u0ab8 \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7 \u0ab2\u0acb\u0a97\u0abf\u0aa8 \u0a95\u0ab0\u0acb.", + "form": { + "email": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0ab8\u0ab0\u0aa8\u0abe\u0aae\u0ac1\u0a82", + "password": "\u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1", + "noAccount": "\u0a96\u0abe\u0aa4\u0ac1\u0a82 \u0aa8\u0aa5\u0ac0?", + "alreadyHaveAccount": "\u0aaa\u0ab9\u0ac7\u0ab2\u0ac7\u0aa5\u0ac0 \u0a9c \u0a96\u0abe\u0aa4\u0ac1\u0a82 \u0a9b\u0ac7?", + "signup": "\u0ab8\u0abe\u0a87\u0aa8 \u0a85\u0aaa \u0a95\u0ab0\u0acb", + "signin": "\u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0acb", + "or": "\u0a85\u0aa5\u0ab5\u0abe", + "continue": "\u0a9a\u0abe\u0ab2\u0ac1 \u0ab0\u0abe\u0a96\u0acb", + "forgotPassword": "\u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0aad\u0ac2\u0ab2\u0ac0 \u0a97\u0aaf\u0abe?", + "passwordMustContain": "\u0aa4\u0aae\u0abe\u0ab0\u0acb \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0ab8\u0aae\u0abe\u0ab5\u0aa4\u0acb \u0a9c \u0ab9\u0acb\u0ab5\u0acb \u0a9c\u0acb\u0a87\u0a8f:", + "emailRequired": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0a8f \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a95\u0acd\u0ab7\u0ac7\u0aa4\u0acd\u0ab0 \u0a9b\u0ac7", + "passwordRequired": "\u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0a8f \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a95\u0acd\u0ab7\u0ac7\u0aa4\u0acd\u0ab0 \u0a9b\u0ac7" + }, + "error": { + "default": "\u0aaa\u0acd\u0ab0\u0ab5\u0ac7\u0ab6 \u0a95\u0ab0\u0ab5\u0abe\u0aae\u0abe\u0a82 \u0a85\u0ab8\u0aae\u0ab0\u0acd\u0aa5.", + "signin": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "oauthsignin": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "redirect_uri_mismatch": "\u0ab0\u0ac0\u0aa1\u0abe\u0aaf\u0ab0\u0ac7\u0a95\u0acd\u0a9f URI \u0a8f oauth \u0a8f\u0aaa\u0acd\u0ab2\u0abf\u0a95\u0ac7\u0ab6\u0aa8 \u0ab0\u0ac2\u0aaa\u0ab0\u0ac7\u0a96\u0abe\u0a82\u0a95\u0aa8 \u0ab8\u0abe\u0aa5\u0ac7 \u0aac\u0a82\u0aa7\u0aac\u0ac7\u0ab8\u0aa4\u0ac0 \u0aa8\u0aa5\u0ac0.", + "oauthcallbackerror": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "oauthcreateaccount": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "emailcreateaccount": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "callback": "\u0a85\u0ab2\u0a97 \u0a96\u0abe\u0aa4\u0abe \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0acb \u0aaa\u0acd\u0ab0\u0aaf\u0aa4\u0acd\u0aa8 \u0a95\u0ab0\u0acb.", + "oauthaccountnotlinked": "\u0aa4\u0aae\u0abe\u0ab0\u0ac0 \u0a93\u0ab3\u0a96\u0aa8\u0ac0 \u0aaa\u0ac1\u0ab7\u0acd\u0a9f\u0abf \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7, \u0aa4\u0aae\u0ac7 \u0a9c\u0ac7 \u0aae\u0ac2\u0ab3\u0aad\u0ac2\u0aa4 \u0ab0\u0ac0\u0aa4\u0ac7 \u0a89\u0aaa\u0aaf\u0acb\u0a97 \u0a95\u0ab0\u0acd\u0aaf\u0acb \u0ab9\u0aa4\u0acb \u0aa4\u0ac7 \u0a9c \u0a8f\u0a95\u0abe\u0a89\u0aa8\u0acd\u0a9f \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0acb.", + "emailsignin": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0aae\u0acb\u0a95\u0ab2\u0ac0 \u0ab6\u0a95\u0abe\u0aaf\u0acb \u0aa8\u0ab9\u0abf.", + "emailverify": "\u0a95\u0ac3\u0aaa\u0abe \u0a95\u0ab0\u0ac0\u0aa8\u0ac7 \u0aa4\u0aae\u0abe\u0ab0\u0abe \u0a87\u0aae\u0ac7\u0a87\u0ab2\u0aa8\u0ac0 \u0a96\u0abe\u0aa4\u0acd\u0ab0\u0ac0 \u0a95\u0ab0\u0acb, \u0a8f\u0a95 \u0aa8\u0ab5\u0ac1\u0a82 \u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0aae\u0acb\u0a95\u0ab2\u0ab5\u0abe\u0aae\u0abe\u0a82 \u0a86\u0ab5\u0acd\u0aaf\u0ac1\u0a82 \u0a9b\u0ac7.", + "credentialssignin": "\u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0aa8\u0abf\u0ab7\u0acd\u0aab\u0ab3. \u0aa4\u0aae\u0ac7 \u0aaa\u0ac2\u0ab0\u0ac0 \u0aaa\u0abe\u0aa1\u0ac7\u0ab2\u0ac0 \u0ab5\u0abf\u0a97\u0aa4\u0acb \u0ab8\u0abe\u0a9a\u0ac0 \u0a9b\u0ac7 \u0aa4\u0ac7 \u0a9a\u0a95\u0abe\u0ab8\u0acb.", + "sessionrequired": "\u0a95\u0ac3\u0aaa\u0abe \u0a95\u0ab0\u0ac0\u0aa8\u0ac7 \u0a86 \u0aaa\u0ac3\u0ab7\u0acd\u0aa0\u0aa8\u0ac7 \u0a8d\u0a95\u0acd\u0ab8\u0ac7\u0ab8 \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a87\u0aa8 \u0a95\u0ab0\u0acb." + } + }, + "authVerifyEmail": { + "almostThere": "\u0aa4\u0aae\u0ac7 \u0aa4\u0acb \u0ab2\u0a97\u0aad\u0a97 \u0aa4\u0acd\u0aaf\u0abe\u0a82 \u0a9c \u0a9b\u0acb! \u0a85\u0aae\u0ac7 \u0a86\u0aa8\u0abe \u0aaa\u0ab0 \u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0aae\u0acb\u0a95\u0ab2\u0acd\u0aaf\u0acb \u0a9b\u0ac7 ", + "verifyEmailLink": "\u0aa4\u0aae\u0abe\u0ab0\u0ac1\u0a82 \u0ab8\u0abe\u0a87\u0aa8\u0a85\u0aaa \u0aaa\u0ac2\u0ab0\u0acd\u0aa3 \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7 \u0a95\u0ac3\u0aaa\u0abe \u0a95\u0ab0\u0ac0\u0aa8\u0ac7 \u0aa4\u0ac7 \u0a87\u0aae\u0ac7\u0a87\u0ab2\u0aa8\u0ac0 \u0ab2\u0abf\u0a82\u0a95 \u0aaa\u0ab0 \u0a95\u0acd\u0ab2\u0abf\u0a95 \u0a95\u0ab0\u0acb.", + "didNotReceive": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0ab6\u0acb\u0aa7\u0ac0 \u0ab6\u0a95\u0aa4\u0abe \u0aa8\u0aa5\u0ac0?", + "resendEmail": "\u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0aab\u0ab0\u0ac0 \u0aae\u0acb\u0a95\u0ab2\u0acb", + "goBack": "\u0aaa\u0abe\u0a9b\u0abe \u0a9c\u0abe\u0a93", + "emailSent": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0ab8\u0aab\u0ab3\u0aa4\u0abe\u0aaa\u0ac2\u0ab0\u0acd\u0ab5\u0a95 \u0aae\u0acb\u0a95\u0ab2\u0abe\u0a88 \u0a97\u0aaf\u0acb.", + "verifyEmail": "\u0aa4\u0aae\u0abe\u0ab0\u0abe \u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0a8f\u0aa1\u0acd\u0ab0\u0ac7\u0ab8\u0aa8\u0ac0 \u0a96\u0abe\u0aa4\u0acd\u0ab0\u0ac0 \u0a95\u0ab0\u0acb" + }, + "providerButton": { + "continue": "{{provider}} \u0ab8\u0abe\u0aa5\u0ac7 \u0a9a\u0abe\u0ab2\u0ac1 \u0ab0\u0abe\u0a96\u0acb", + "signup": "{{provider}} \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0a87\u0aa8 \u0a85\u0aaa \u0a95\u0ab0\u0acb" + }, + "authResetPassword": { + "newPasswordRequired": "\u0aa8\u0ab5\u0acb \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0a8f \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a95\u0acd\u0ab7\u0ac7\u0aa4\u0acd\u0ab0 \u0a9b\u0ac7", + "passwordsMustMatch": "\u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1\u0acb \u0aac\u0a82\u0aa7\u0aac\u0ac7\u0ab8\u0aa4\u0abe \u0a9c \u0ab9\u0acb\u0ab5\u0abe \u0a9c\u0acb\u0a88\u0a8f", + "confirmPasswordRequired": "\u0a96\u0abe\u0aa4\u0ab0\u0ac0 \u0a95\u0ab0\u0acb \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0a8f \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a95\u0acd\u0ab7\u0ac7\u0aa4\u0acd\u0ab0 \u0a9b\u0ac7", + "newPassword": "\u0aa8\u0ab5\u0acb \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1", + "confirmPassword": "\u0a96\u0abe\u0aa4\u0ab0\u0ac0 \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1", + "resetPassword": "\u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1\u0aa8\u0ac7 \u0aaa\u0ac1\u0aa8:\u0ab8\u0ac1\u0aaf\u0acb\u0a9c\u0abf\u0aa4 \u0a95\u0ab0\u0acb" + }, + "authForgotPassword": { + "email": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0ab8\u0ab0\u0aa8\u0abe\u0aae\u0ac1\u0a82", + "emailRequired": "\u0a88-\u0aae\u0ac7\u0a88\u0ab2 \u0a8f \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a95\u0acd\u0ab7\u0ac7\u0aa4\u0acd\u0ab0 \u0a9b\u0ac7", + "emailSent": "\u0ab8\u0ac2\u0a9a\u0aa8\u0abe\u0a93 \u0aae\u0abe\u0a9f\u0ac7 \u0a95\u0ac3\u0aaa\u0abe \u0a95\u0ab0\u0ac0\u0aa8\u0ac7 \u0aa4\u0aae\u0abe\u0ab0\u0ac1\u0a82 \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0ab0\u0abf\u0ab8\u0ac5\u0a9f \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7 \u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0a8f\u0aa1\u0acd\u0ab0\u0ac7\u0ab8 {{email}} \u0a9a\u0a95\u0abe\u0ab8\u0acb.", + "enterEmail": "\u0aa4\u0aae\u0abe\u0ab0\u0ac1\u0a82 \u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0a8f\u0aa1\u0acd\u0ab0\u0ac7\u0ab8 \u0aa6\u0abe\u0a96\u0ab2 \u0a95\u0ab0\u0acb \u0a85\u0aa8\u0ac7 \u0a85\u0aae\u0ac7 \u0aa4\u0aae\u0abe\u0ab0\u0acb \u0aaa\u0abe\u0ab8\u0ab5\u0ab0\u0acd\u0aa1 \u0ab0\u0ac0\u0ab8\u0ac7\u0a9f \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7 \u0aa4\u0aae\u0aa8\u0ac7 \u0ab8\u0ac2\u0a9a\u0aa8\u0abe\u0a93 \u0aae\u0acb\u0a95\u0ab2\u0ac0\u0ab6\u0ac1\u0a82.", + "resendEmail": "\u0a87\u0aae\u0ac7\u0a87\u0ab2 \u0aab\u0ab0\u0ac0 \u0aae\u0acb\u0a95\u0ab2\u0acb", + "continue": "\u0a9a\u0abe\u0ab2\u0ac1 \u0ab0\u0abe\u0a96\u0acb", + "goBack": "\u0aaa\u0abe\u0a9b\u0abe \u0a9c\u0abe\u0a93" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0a87\u0aa4\u0abf\u0ab9\u0abe\u0ab8 \u0aac\u0aa4\u0abe\u0ab5\u0acb", + "lastInputs": "\u0a9b\u0ac7\u0ab2\u0acd\u0ab2\u0abe \u0a87\u0aa8\u0aaa\u0ac1\u0a9f\u0acd\u0ab8", + "noInputs": "\u0a86\u0ab5\u0abe \u0a96\u0abe\u0ab2\u0ac0...", + "loading": "\u0ab2\u0acb\u0aa1 \u0a95\u0ab0\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac7..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0aa4\u0aae\u0abe\u0ab0\u0acb \u0ab8\u0a82\u0aa6\u0ac7\u0ab6\u0acb \u0a85\u0ab9\u0ac0\u0a82 \u0a9f\u0abe\u0a87\u0aaa \u0a95\u0ab0\u0acb..." + }, + "speechButton": { + "start": "\u0ab0\u0ac7\u0a95\u0acb\u0ab0\u0acd\u0aa1 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0ac1\u0a82 \u0ab6\u0ab0\u0ac2 \u0a95\u0ab0\u0acb", + "stop": "\u0ab0\u0ac7\u0a95\u0acb\u0ab0\u0acd\u0aa1 \u0a95\u0ab0\u0ab5\u0abe\u0aa8\u0ac1\u0a82 \u0aac\u0a82\u0aa7 \u0a95\u0ab0\u0acb" + }, + "SubmitButton": { + "sendMessage": "\u0ab8\u0a82\u0aa6\u0ac7\u0ab6\u0acb \u0aae\u0acb\u0a95\u0ab2\u0acb", + "stopTask": "\u0a95\u0abe\u0ab0\u0acd\u0aaf\u0aa8\u0ac7 \u0a85\u0a9f\u0a95\u0abe\u0ab5\u0acb" + }, + "UploadButton": { + "attachFiles": "\u0aab\u0abe\u0a87\u0ab2\u0acb\u0aa8\u0ac7 \u0a9c\u0acb\u0aa1\u0acb" + }, + "waterMark": { + "text": "\u0aa8\u0ac0 \u0ab8\u0abe\u0aa5\u0ac7 \u0aac\u0abf\u0ab2\u0acd\u0a9f \u0aa5\u0aaf\u0ac7\u0ab2" + } + }, + "Messages": { + "index": { + "running": "\u0a9a\u0abe\u0ab2\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0ac1 \u0a9b\u0ac7", + "executedSuccessfully": "\u0ab8\u0aab\u0ab3\u0aa4\u0abe\u0aaa\u0ac2\u0ab0\u0acd\u0ab5\u0a95 \u0a9a\u0ab2\u0abe\u0ab5\u0acd\u0aaf\u0ac7\u0ab2 \u0a9b\u0ac7", + "failed": "\u0aa8\u0abf\u0ab7\u0acd\u0aab\u0ab3", + "feedbackUpdated": "\u0aaa\u0acd\u0ab0\u0aa4\u0abf\u0ab8\u0abe\u0aa6 \u0ab8\u0ac1\u0aa7\u0abe\u0ab0\u0ac7\u0ab2 \u0a9b\u0ac7", + "updating": "\u0ab8\u0ac1\u0aa7\u0abe\u0ab0\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac0\u0a8f" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0aa4\u0aae\u0abe\u0ab0\u0ac0 \u0aab\u0abe\u0a87\u0ab2\u0acb\u0aa8\u0ac7 \u0a85\u0a82\u0ab9\u0abf \u0aae\u0ac2\u0a95\u0acb" + }, + "index": { + "failedToUpload": "\u0a85\u0aaa\u0ab2\u0acb\u0aa1 \u0a95\u0ab0\u0ab5\u0abe\u0aae\u0abe\u0a82 \u0aa8\u0abf\u0ab7\u0acd\u0aab\u0ab3", + "cancelledUploadOf": "\u0aa8\u0ac1\u0a82 \u0a85\u0aaa\u0ab2\u0acb\u0aa1 \u0ab0\u0aa6 \u0aa5\u0aaf\u0ac7\u0ab2 \u0a9b\u0ac7", + "couldNotReachServer": "\u0ab8\u0ab0\u0acd\u0ab5\u0ab0 \u0ab8\u0ac1\u0aa7\u0ac0 \u0aaa\u0ab9\u0acb\u0a82\u0a9a\u0ac0 \u0ab6\u0a95\u0acd\u0aaf\u0abe \u0aa8\u0ab9\u0abf\u0a82", + "continuingChat": "\u0aaa\u0ab9\u0ac7\u0ab2\u0abe\u0aa8\u0ac0 \u0ab5\u0abe\u0aa4\u0a9a\u0ac0\u0aa4\u0aa8\u0ac7 \u0a9a\u0abe\u0ab2\u0ac1 \u0ab0\u0abe\u0a96\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac7" + }, + "settings": { + "settingsPanel": "\u0aaa\u0ac7\u0aa8\u0ab2 \u0ab8\u0ac1\u0aaf\u0acb\u0a9c\u0aa8\u0acb", + "reset": "\u0aaa\u0ac1\u0aa8:\u0ab8\u0ac1\u0aaf\u0acb\u0a9c\u0abf\u0aa4 \u0a95\u0ab0\u0acb", + "cancel": "\u0ab0\u0aa6\u0acd\u0aa6", + "confirm": "\u0a96\u0abe\u0aa4\u0ab0\u0ac0 \u0a95\u0ab0\u0acb" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u0aaa\u0acd\u0ab0\u0aa4\u0abf\u0ab8\u0abe\u0aa6: \u0aac\u0aa7\u0abe", + "feedbackPositive": "\u0aaa\u0acd\u0ab0\u0aa4\u0abf\u0ab8\u0abe\u0aa6: \u0ab9\u0a95\u0abe\u0ab0\u0abe\u0aa4\u0acd\u0aae\u0a95", + "feedbackNegative": "\u0aaa\u0acd\u0ab0\u0aa4\u0abf\u0ab8\u0abe\u0aa6: \u0aa8\u0a95\u0abe\u0ab0\u0abe\u0aa4\u0acd\u0aae\u0a95" + }, + "SearchBar": { + "search": "\u0ab6\u0acb\u0aa7\u0ab5\u0ac1\u0a82" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0a86 \u0aa5\u0acd\u0ab0\u0ac7\u0aa1\u0aa8\u0ac0 \u0ab8\u0abe\u0aa5\u0ac7 \u0ab8\u0abe\u0aa5\u0ac7 \u0aa4\u0ac7\u0aa8\u0abe \u0ab8\u0a82\u0aa6\u0ac7\u0ab6\u0abe \u0a85\u0aa8\u0ac7 \u0aa4\u0aa4\u0acd\u0ab5\u0acb\u0aa8\u0ac7 \u0aaa\u0aa3 \u0a95\u0abe\u0aa2\u0ac0 \u0aa8\u0abe\u0a96\u0ab6\u0ac7.", + "cancel": "\u0ab0\u0aa6\u0acd\u0aa6", + "confirm": "\u0a96\u0abe\u0aa4\u0ab0\u0ac0 \u0a95\u0ab0\u0acb", + "deletingChat": "\u0a9a\u0ac5\u0a9f\u0aa8\u0ac7 \u0a95\u0abe\u0aa2\u0ac0 \u0ab0\u0ab9\u0acd\u0aaf\u0abe \u0a9b\u0ac0\u0a8f", + "chatDeleted": "\u0a9a\u0ac5\u0a9f \u0aa1\u0abf\u0ab2\u0ac0\u0a9f \u0aa5\u0a88 \u0a97\u0a88" + }, + "index": { + "pastChats": "\u0aad\u0ac2\u0aa4\u0a95\u0abe\u0ab3\u0aa8\u0ac0 \u0ab5\u0abe\u0aa4\u0a9a\u0ac0\u0aa4\u0acb" + }, + "ThreadList": { + "empty": "\u0a96\u0abe\u0ab2\u0ac0...", + "today": "\u0a86\u0a9c\u0ac7", + "yesterday": "\u0a97\u0a87\u0a95\u0abe\u0ab2\u0ac7", + "previous7days": "\u0aaa\u0ab9\u0ac7\u0ab2\u0abe\u0aa8\u0abe \u0aed \u0aa6\u0abf\u0ab5\u0ab8\u0acb", + "previous30days": "\u0aaa\u0ab9\u0ac7\u0ab2\u0abe\u0aa8\u0abe \u0ae9\u0ae6 \u0aa6\u0abf\u0ab5\u0ab8\u0acb" + }, + "TriggerButton": { + "closeSidebar": "\u0aac\u0abe\u0a9c\u0ac1\u0aaa\u0a9f\u0acd\u0a9f\u0ac0\u0aa8\u0ac7 \u0aac\u0a82\u0aa7 \u0a95\u0ab0\u0acb", + "openSidebar": "\u0aac\u0abe\u0a9c\u0ac1\u0aaa\u0a9f\u0acd\u0a9f\u0ac0 \u0a96\u0acb\u0ab2\u0acb" + } + }, + "Thread": { + "backToChat": "\u0ab8\u0a82\u0ab5\u0abe\u0aa6\u0aae\u0abe\u0a82 \u0aaa\u0abe\u0a9b\u0abe \u0a9c\u0abe\u0a93", + "chatCreatedOn": "\u0a86 \u0ab5\u0abe\u0aa4\u0a9a\u0ac0\u0aa4 \u0aa4\u0ac7\u0aa8\u0ac0 \u0aaa\u0ab0 \u0aac\u0aa8\u0abe\u0ab5\u0ac7\u0ab2 \u0ab9\u0aa4\u0ac0" + } + }, + "header": { + "chat": "\u0ab8\u0a82\u0ab5\u0abe\u0aa6", + "readme": "\u0ab0\u0ac0\u0aa1\u0aae\u0ac7" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u0aaa\u0acd\u0ab0\u0aa6\u0abe\u0aa4\u0abe\u0a93\u0aa8\u0ac7 \u0ab2\u0abe\u0ab5\u0ab5\u0abe\u0aae\u0abe\u0a82 \u0aa8\u0abf\u0ab7\u0acd\u0aab\u0ab3\u0aa4\u0abe:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0ab8\u0aab\u0ab3\u0aa4\u0abe\u0aaa\u0ac2\u0ab0\u0acd\u0ab5\u0a95 \u0ab8\u0a82\u0a97\u0acd\u0ab0\u0ab9\u0abe\u0aaf\u0ac7\u0ab2", + "requiredApiKeys": "\u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 API \u0a95\u0ac0\u0a93", + "requiredApiKeysInfo": "\u0a86 \u0a8f\u0aaa\u0acd\u0ab2\u0abf\u0a95\u0ac7\u0ab6\u0aa8\u0aa8\u0acb \u0a89\u0aaa\u0aaf\u0acb\u0a97 \u0a95\u0ab0\u0ab5\u0abe \u0aae\u0abe\u0a9f\u0ac7, \u0aa8\u0ac0\u0a9a\u0ac7\u0aa8\u0ac0 API \u0a95\u0ac0\u0a93 \u0a9c\u0ab0\u0ac2\u0ab0\u0ac0 \u0a9b\u0ac7. \u0a95\u0ac0\u0a93 \u0aa4\u0aae\u0abe\u0ab0\u0abe \u0aa1\u0abf\u0ab5\u0abe\u0a87\u0ab8\u0aa8\u0abe \u0ab8\u0acd\u0aa5\u0abe\u0aa8\u0abf\u0a95 \u0ab8\u0acd\u0a9f\u0acb\u0ab0\u0ac7\u0a9c \u0aaa\u0ab0 \u0ab8\u0a82\u0a97\u0acd\u0ab0\u0ab9\u0abf\u0aa4 \u0aa5\u0abe\u0aaf \u0a9b\u0ac7." + }, + "Page": { + "notPartOfProject": "\u0aa4\u0aae\u0ac7 \u0a86 \u0aaa\u0acd\u0ab0\u0acb\u0a9c\u0ac7\u0a95\u0acd\u0a9f\u0aa8\u0acb \u0aad\u0abe\u0a97 \u0aa8\u0aa5\u0ac0." + }, + "ResumeButton": { + "resumeChat": "\u0aab\u0ab0\u0ac0 \u0ab6\u0ab0\u0ac2 \u0a95\u0ab0\u0acb \u0ab8\u0a82\u0ab5\u0abe\u0aa6" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/he-IL.json b/examples/chatbot/.chainlit/translations/he-IL.json new file mode 100644 index 000000000..c367dfa8a --- /dev/null +++ b/examples/chatbot/.chainlit/translations/he-IL.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea", + "settingsKey": "S", + "APIKeys": "API Keys", + "logout": "\u05d4\u05ea\u05e0\u05ea\u05e7" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u05e6'\u05d0\u05d8 \u05d7\u05d3\u05e9" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f Task List", + "loading": "\u05d8\u05d5\u05e2\u05df...", + "error": "\u05e9\u05d2\u05d9\u05d0\u05d4" + } + }, + "attachments": { + "cancelUpload": "\u05d1\u05d8\u05dc \u05d4\u05e2\u05dc\u05d0\u05d4", + "removeAttachment": "\u05d4\u05e1\u05e8 \u05e7\u05d5\u05d1\u05e5 \u05de\u05e6\u05d5\u05e8\u05e3" + }, + "newChatDialog": { + "createNewChat": "\u05e6\u05d5\u05e8 \u05e6'\u05d0\u05d8 \u05d7\u05d3\u05e9?", + "clearChat": "\u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5 \u05ea\u05e0\u05e7\u05d4 \u05d0\u05ea \u05d4\u05d4\u05d5\u05d3\u05e2\u05d5\u05ea \u05d4\u05e0\u05d5\u05db\u05d7\u05d9\u05d5\u05ea \u05d5\u05ea\u05ea\u05d7\u05d9\u05dc \u05e6'\u05d0\u05d8 \u05d7\u05d3\u05e9.", + "cancel": "\u05d1\u05d8\u05dc", + "confirm": "\u05d0\u05e9\u05e8" + }, + "settingsModal": { + "settings": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea", + "expandMessages": "\u05d4\u05e8\u05d7\u05d1 \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea", + "hideChainOfThought": "\u05d4\u05e1\u05ea\u05e8 \u05e9\u05e8\u05e9\u05e8\u05ea \u05de\u05d7\u05e9\u05d1\u05d5\u05ea", + "darkMode": "\u05de\u05e6\u05d1 \u05db\u05d4\u05d4" + }, + "detailsButton": { + "using": "\u05de\u05e9\u05ea\u05de\u05e9 \u05d1-", + "running": "\u05e8\u05e5", + "took_one": "\u05dc\u05e7\u05d7 \u05e6\u05e2\u05d3 {{count}}", + "took_other": "\u05dc\u05e7\u05d7 \u05e6\u05e2\u05d3\u05d9\u05dd {{count}}" + }, + "auth": { + "authLogin": { + "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05db\u05d3\u05d9 \u05dc\u05d2\u05e9\u05ea \u05dc\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4.", + "form": { + "email": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05d9\u05de\u05d9\u05d9\u05dc", + "password": "\u05e1\u05d9\u05e1\u05de\u05d0", + "noAccount": "\u05d0\u05d9\u05df \u05dc\u05da \u05d7\u05e9\u05d1\u05d5\u05df?", + "alreadyHaveAccount": "\u05db\u05d1\u05e8 \u05d9\u05e9 \u05dc\u05da \u05d7\u05e9\u05d1\u05d5\u05df?", + "signup": "\u05d4\u05d9\u05e8\u05e9\u05dd", + "signin": "\u05d4\u05d9\u05db\u05e0\u05e1", + "or": "\u05d0\u05d5", + "continue": "\u05d4\u05de\u05e9\u05da", + "forgotPassword": "\u05e9\u05db\u05d7\u05ea \u05e1\u05d9\u05e1\u05de\u05d4?", + "passwordMustContain": "\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05dc\u05da \u05d7\u05d9\u05d9\u05d1\u05ea \u05dc\u05d4\u05db\u05d9\u05dc:", + "emailRequired": "\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05d4\u05d5\u05d0 \u05e9\u05d3\u05d4 \u05d7\u05d5\u05d1\u05d4", + "passwordRequired": "\u05e1\u05d9\u05e1\u05de\u05d4 \u05d4\u05d9\u05d0 \u05e9\u05d3\u05d4 \u05d7\u05d5\u05d1\u05d4" + }, + "error": { + "default": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05dc\u05d4\u05d9\u05db\u05e0\u05e1.", + "signin": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "oauthsignin": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "redirect_uri_mismatch": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-URI \u05dc\u05d4\u05e4\u05e0\u05d9\u05d4 \u05d0\u05d9\u05e0\u05d4 \u05ea\u05d5\u05d0\u05de\u05ea \u05dc\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4 \u05e9\u05dc oauth.", + "oauthcallbackerror": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "oauthcreateaccount": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "emailcreateaccount": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "callback": "\u05e0\u05e1\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d7\u05e9\u05d1\u05d5\u05df \u05d0\u05d7\u05e8.", + "oauthaccountnotlinked": "\u05db\u05d3\u05d9 \u05dc\u05d0\u05e9\u05e8 \u05d0\u05ea \u05d6\u05d4\u05d5\u05ea\u05da, \u05d4\u05d9\u05db\u05e0\u05e1 \u05e2\u05dd \u05d0\u05d5\u05ea\u05d5 \u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05d1\u05d5 \u05d4\u05e9\u05ea\u05de\u05e9\u05ea \u05d1\u05de\u05e7\u05d5\u05e8.", + "emailsignin": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05dc\u05e9\u05dc\u05d5\u05d7 \u05d0\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc.", + "emailverify": "\u05d0\u05e0\u05d0 \u05d0\u05e9\u05e8 \u05d0\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05e9\u05dc\u05da, \u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05d7\u05d3\u05e9 \u05e0\u05e9\u05dc\u05d7.", + "credentialssignin": "\u05d4\u05db\u05e0\u05d9\u05e1\u05d4 \u05e0\u05db\u05e9\u05dc\u05d4. \u05d1\u05d3\u05d5\u05e7 \u05e9\u05d4\u05e4\u05e8\u05d8\u05d9\u05dd \u05e9\u05e1\u05d9\u05e4\u05e7\u05ea \u05e0\u05db\u05d5\u05e0\u05d9\u05dd.", + "sessionrequired": "\u05d0\u05e0\u05d0 \u05d4\u05d9\u05db\u05e0\u05e1 \u05db\u05d3\u05d9 \u05dc\u05d2\u05e9\u05ea \u05dc\u05d3\u05e3 \u05d6\u05d4." + } + }, + "authVerifyEmail": { + "almostThere": "\u05d0\u05ea\u05d4 \u05db\u05de\u05e2\u05d8 \u05e9\u05dd! \u05e9\u05dc\u05d7\u05e0\u05d5 \u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05d0\u05dc ", + "verifyEmailLink": "\u05d0\u05e0\u05d0 \u05dc\u05d7\u05e5 \u05e2\u05dc \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8 \u05d1\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05d6\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05e9\u05dc\u05d9\u05dd \u05d0\u05ea \u05d4\u05d4\u05e8\u05e9\u05de\u05d4 \u05e9\u05dc\u05da.", + "didNotReceive": "\u05dc\u05d0 \u05de\u05d5\u05e6\u05d0 \u05d0\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc?", + "resendEmail": "\u05e9\u05dc\u05d7 \u05e9\u05d5\u05d1 \u05d0\u05d9\u05de\u05d9\u05d9\u05dc", + "goBack": "\u05d7\u05d6\u05d5\u05e8 \u05d0\u05d7\u05d5\u05e8\u05d4", + "emailSent": "\u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05e0\u05e9\u05dc\u05d7 \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4.", + "verifyEmail": "\u05d0\u05de\u05ea \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05e9\u05dc\u05da" + }, + "providerButton": { + "continue": "\u05d4\u05de\u05e9\u05da \u05e2\u05dd {{provider}}", + "signup": "\u05d4\u05d9\u05e8\u05e9\u05dd \u05e2\u05dd {{provider}}" + }, + "authResetPassword": { + "newPasswordRequired": "\u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3\u05e9\u05d4 \u05d4\u05d9\u05d0 \u05e9\u05d3\u05d4 \u05d7\u05d5\u05d1\u05d4", + "passwordsMustMatch": "\u05d4\u05e1\u05d9\u05e1\u05de\u05d0\u05d5\u05ea \u05d7\u05d9\u05d9\u05d1\u05d5\u05ea \u05dc\u05d4\u05ea\u05d0\u05d9\u05dd", + "confirmPasswordRequired": "\u05d0\u05d9\u05e9\u05d5\u05e8 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d4\u05d5\u05d0 \u05e9\u05d3\u05d4 \u05d7\u05d5\u05d1\u05d4", + "newPassword": "\u05e1\u05d9\u05e1\u05de\u05d0 \u05d7\u05d3\u05e9\u05d4", + "confirmPassword": "\u05d0\u05e9\u05e8 \u05e1\u05d9\u05e1\u05de\u05d0", + "resetPassword": "\u05d0\u05e4\u05e1 \u05e1\u05d9\u05e1\u05de\u05d4" + }, + "authForgotPassword": { + "email": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05d9\u05de\u05d9\u05d9\u05dc", + "emailRequired": "\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05d4\u05d5\u05d0 \u05e9\u05d3\u05d4 \u05d7\u05d5\u05d1\u05d4", + "emailSent": "\u05d0\u05e0\u05d0 \u05d1\u05d3\u05d5\u05e7 \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc {{email}} \u05dc\u05e7\u05d1\u05dc\u05ea \u05d4\u05d5\u05e8\u05d0\u05d5\u05ea \u05dc\u05d0\u05d9\u05e4\u05d5\u05e1 \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05dc\u05da.", + "enterEmail": "\u05d4\u05d6\u05df \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05d9\u05de\u05d9\u05d9\u05dc \u05e9\u05dc\u05da \u05d5\u05d0\u05e0\u05d5 \u05e0\u05e9\u05dc\u05d7 \u05dc\u05da \u05d4\u05d5\u05e8\u05d0\u05d5\u05ea \u05dc\u05d0\u05d9\u05e4\u05d5\u05e1 \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05dc\u05da.", + "resendEmail": "\u05e9\u05dc\u05d7 \u05e9\u05d5\u05d1 \u05d0\u05d9\u05de\u05d9\u05d9\u05dc", + "continue": "\u05d4\u05de\u05e9\u05da", + "goBack": "\u05d7\u05d6\u05d5\u05e8 \u05d0\u05d7\u05d5\u05e8\u05d4" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u05d4\u05e6\u05d2 \u05d4\u05d9\u05e1\u05d8\u05d5\u05e8\u05d9\u05d4", + "lastInputs": "\u05e7\u05dc\u05d8 \u05d0\u05d7\u05e8\u05d5\u05df", + "noInputs": "\u05e8\u05d9\u05e7...", + "loading": "\u05d8\u05d5\u05e2\u05df..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u05db\u05ea\u05d5\u05d1 \u05d4\u05d5\u05d3\u05e2\u05d4 \u05db\u05d0\u05df..." + }, + "speechButton": { + "start": "\u05d4\u05ea\u05d7\u05dc \u05d4\u05e7\u05dc\u05d8\u05d4", + "stop": "\u05e2\u05e6\u05d5\u05e8 \u05d4\u05e7\u05dc\u05d8\u05d4" + }, + "SubmitButton": { + "sendMessage": "\u05e9\u05dc\u05d7 \u05d4\u05d5\u05d3\u05e2\u05d4", + "stopTask": "\u05e2\u05e6\u05d5\u05e8 \u05de\u05e9\u05d9\u05de\u05d4" + }, + "UploadButton": { + "attachFiles": "\u05e6\u05e8\u05e3 \u05e7\u05d1\u05e6\u05d9\u05dd" + }, + "waterMark": { + "text": "\u05e0\u05d1\u05e0\u05d4 \u05e2\u05dd" + } + }, + "Messages": { + "index": { + "running": "\u05e8\u05e5", + "executedSuccessfully": "\u05d1\u05d5\u05e6\u05e2 \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4", + "failed": "\u05e0\u05db\u05e9\u05dc", + "feedbackUpdated": "\u05de\u05e9\u05d5\u05d1 \u05e2\u05d5\u05d3\u05db\u05df", + "updating": "\u05de\u05e2\u05d3\u05db\u05df" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u05e9\u05d7\u05e8\u05e8 \u05d0\u05ea \u05d4\u05e7\u05d1\u05e6\u05d9\u05dd \u05e9\u05dc\u05da \u05db\u05d0\u05df" + }, + "index": { + "failedToUpload": "\u05d4\u05e2\u05dc\u05d0\u05d4 \u05e0\u05db\u05e9\u05dc\u05d4", + "cancelledUploadOf": "\u05d4\u05e2\u05dc\u05d0\u05d4 \u05e9\u05dc \u05d1\u05d5\u05d8\u05dc\u05d4", + "couldNotReachServer": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05d4\u05d9\u05d4 \u05dc\u05d4\u05d2\u05d9\u05e2 \u05dc\u05e9\u05e8\u05ea", + "continuingChat": "\u05de\u05de\u05e9\u05d9\u05da \u05d1\u05e6'\u05d0\u05d8 \u05d4\u05e7\u05d5\u05d3\u05dd" + }, + "settings": { + "settingsPanel": "\u05dc\u05d5\u05d7 \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea", + "reset": "\u05d0\u05e4\u05e1", + "cancel": "\u05d1\u05d8\u05dc", + "confirm": "\u05d0\u05e9\u05e8" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u05de\u05e9\u05d5\u05d1: \u05d4\u05db\u05dc", + "feedbackPositive": "\u05de\u05e9\u05d5\u05d1: \u05d7\u05d9\u05d5\u05d1\u05d9", + "feedbackNegative": "\u05de\u05e9\u05d5\u05d1: \u05e9\u05dc\u05d9\u05dc\u05d9" + }, + "SearchBar": { + "search": "\u05d7\u05d9\u05e4\u05d5\u05e9" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5 \u05ea\u05de\u05d7\u05e7 \u05d0\u05ea \u05d4\u05e9\u05e8\u05e9\u05d5\u05e8 \u05d5\u05db\u05df \u05d0\u05ea \u05d4\u05d4\u05d5\u05d3\u05e2\u05d5\u05ea \u05d5\u05d4\u05e8\u05db\u05d9\u05d1\u05d9\u05dd \u05e9\u05dc\u05d5.", + "cancel": "\u05d1\u05d8\u05dc", + "confirm": "\u05d0\u05e9\u05e8", + "deletingChat": "\u05de\u05d5\u05d7\u05e7 \u05e6'\u05d0\u05d8", + "chatDeleted": "\u05d4\u05e6'\u05d0\u05d8 \u05e0\u05de\u05d7\u05e7" + }, + "index": { + "pastChats": "\u05e6'\u05d0\u05d8\u05d9\u05dd \u05e7\u05d5\u05d3\u05de\u05d9\u05dd" + }, + "ThreadList": { + "empty": "\u05e8\u05d9\u05e7...", + "today": "\u05d4\u05d9\u05d5\u05dd", + "yesterday": "\u05d0\u05ea\u05de\u05d5\u05dc", + "previous7days": "7 \u05d9\u05de\u05d9\u05dd \u05e7\u05d5\u05d3\u05de\u05d9\u05dd", + "previous30days": "30 \u05d9\u05de\u05d9\u05dd \u05e7\u05d5\u05d3\u05de\u05d9\u05dd" + }, + "TriggerButton": { + "closeSidebar": "\u05e1\u05d2\u05d5\u05e8 \u05e1\u05e8\u05d2\u05dc \u05e6\u05d3", + "openSidebar": "\u05e4\u05ea\u05d7 \u05e1\u05e8\u05d2\u05dc \u05e6\u05d3" + } + }, + "Thread": { + "backToChat": "\u05d7\u05d6\u05d5\u05e8 \u05dc\u05e6'\u05d0\u05d8", + "chatCreatedOn": "\u05d4\u05e6'\u05d0\u05d8 \u05d4\u05d6\u05d4 \u05e0\u05d5\u05e6\u05e8 \u05d1\u05ea\u05d0\u05e8\u05d9\u05da" + } + }, + "header": { + "chat": "\u05e6'\u05d0\u05d8", + "readme": "\u05d0\u05d5\u05d3\u05d5\u05ea" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u05e0\u05db\u05e9\u05dc\u05d4 \u05d4\u05d1\u05d0\u05ea \u05e1\u05e4\u05e7\u05d9\u05dd:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u05e0\u05e9\u05de\u05e8 \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4", + "requiredApiKeys": "\u05de\u05e4\u05ea\u05d7\u05d5\u05ea API \u05e0\u05d3\u05e8\u05e9\u05d9\u05dd", + "requiredApiKeysInfo": "\u05db\u05d3\u05d9 \u05dc\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4 \u05d6\u05d5, \u05e0\u05d3\u05e8\u05e9\u05d9\u05dd \u05de\u05e4\u05ea\u05d7\u05d5\u05ea \u05d4-API \u05d4\u05d1\u05d0\u05d9\u05dd. \u05d4\u05de\u05e4\u05ea\u05d7\u05d5\u05ea \u05de\u05d0\u05d5\u05d7\u05e1\u05e0\u05d9\u05dd \u05d1\u05d0\u05d7\u05e1\u05d5\u05df \u05d4\u05de\u05e7\u05d5\u05de\u05d9 \u05e9\u05dc \u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05dc\u05da." + }, + "Page": { + "notPartOfProject": "\u05d0\u05ea\u05d4 \u05dc\u05d0 \u05d7\u05dc\u05e7 \u05de\u05d4\u05e4\u05e8\u05d5\u05d9\u05e7\u05d8 \u05d4\u05d6\u05d4." + }, + "ResumeButton": { + "resumeChat": "\u05d4\u05de\u05e9\u05da \u05e6'\u05d0\u05d8" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/hi.json b/examples/chatbot/.chainlit/translations/hi.json new file mode 100644 index 000000000..26b8844bb --- /dev/null +++ b/examples/chatbot/.chainlit/translations/hi.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938", + "settingsKey": "\u0926\u0915\u094d\u0937\u093f\u0923\u0940", + "APIKeys": "\u090f\u092a\u0940\u0906\u0908 \u0915\u0941\u0902\u091c\u0940", + "logout": "\u0932\u0949\u0917\u0906\u0909\u091f" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0928\u0908 \u091a\u0948\u091f" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0915\u093e\u0930\u094d\u092f \u0938\u0942\u091a\u0940", + "loading": "\u0932\u094b\u0921\u0964\u0964\u0964", + "error": "\u0915\u094b\u0908 \u0924\u094d\u0930\u0941\u091f\u093f \u0909\u0924\u094d\u092a\u0928\u094d\u0928 \u0939\u0941\u0908" + } + }, + "attachments": { + "cancelUpload": "\u0905\u092a\u0932\u094b\u0921 \u0930\u0926\u094d\u0926 \u0915\u0930\u0947\u0902", + "removeAttachment": "\u0905\u0928\u0941\u0932\u0917\u094d\u0928\u0915 \u0928\u093f\u0915\u093e\u0932\u0947\u0902" + }, + "newChatDialog": { + "createNewChat": "\u0928\u0908 \u091a\u0948\u091f \u092c\u0928\u093e\u090f\u0901?", + "clearChat": "\u092f\u0939 \u0935\u0930\u094d\u0924\u092e\u093e\u0928 \u0938\u0902\u0926\u0947\u0936\u094b\u0902 \u0915\u094b \u0938\u093e\u092b\u093c \u0915\u0930\u0947\u0917\u093e \u0914\u0930 \u090f\u0915 \u0928\u0908 \u091a\u0948\u091f \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0917\u093e\u0964", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u0928\u093e", + "confirm": "\u0938\u0941\u0926\u0943\u0922\u093c \u0915\u0930\u0928\u093e" + }, + "settingsModal": { + "settings": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938", + "expandMessages": "\u0938\u0902\u0926\u0947\u0936\u094b\u0902 \u0915\u093e \u0935\u093f\u0938\u094d\u0924\u093e\u0930 \u0915\u0930\u0947\u0902", + "hideChainOfThought": "\u0935\u093f\u091a\u093e\u0930 \u0915\u0940 \u0936\u094d\u0930\u0943\u0902\u0916\u0932\u093e \u091b\u093f\u092a\u093e\u090f\u0902", + "darkMode": "\u0921\u093e\u0930\u094d\u0915 \u092e\u094b\u0921" + }, + "detailsButton": { + "using": "\u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0915\u0947", + "running": "\u092d\u093e\u0917\u0928\u093e", + "took_one": "{{count}} \u0915\u0926\u092e \u0909\u0920\u093e\u092f\u093e", + "took_other": "{{count}} \u0915\u0926\u092e \u0909\u0920\u093e\u090f" + }, + "auth": { + "authLogin": { + "title": "\u0910\u092a \u0924\u0915 \u092a\u0939\u0941\u0902\u091a\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u0932\u0949\u0917\u093f\u0928 \u0915\u0930\u0947\u0902\u0964", + "form": { + "email": "\u0908\u092e\u0947\u0932 \u092a\u0924\u093e", + "password": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921", + "noAccount": "\u0915\u094d\u092f\u093e \u0906\u092a\u0915\u0947 \u092a\u093e\u0938 \u0916\u093e\u0924\u093e \u0928\u0939\u0940\u0902 \u0939\u0948?", + "alreadyHaveAccount": "\u092a\u0939\u0932\u0947 \u0938\u0947 \u0939\u0940 \u090f\u0915 \u0916\u093e\u0924\u093e \u0939\u0948?", + "signup": "\u0928\u093e\u092e \u0932\u093f\u0916\u094b", + "signin": "\u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0947\u0902", + "or": "\u0928\u0939\u0940\u0902 \u0924\u094b", + "continue": "\u091c\u093e\u0930\u0940 \u0930\u0916\u0928\u093e", + "forgotPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u092d\u0942\u0932 \u0917\u090f?", + "passwordMustContain": "\u0906\u092a\u0915\u0947 \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u092e\u0947\u0902 \u0939\u094b\u0928\u093e \u091a\u093e\u0939\u093f\u090f:", + "emailRequired": "\u0908\u092e\u0947\u0932 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u092b\u093c\u0940\u0932\u094d\u0921 \u0939\u0948", + "passwordRequired": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u092b\u093c\u0940\u0932\u094d\u0921 \u0939\u0948" + }, + "error": { + "default": "\u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0928\u0947 \u092e\u0947\u0902 \u0905\u0938\u092e\u0930\u094d\u0925.", + "signin": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "oauthsignin": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "redirect_uri_mismatch": "\u0930\u0940\u0921\u093e\u092f\u0930\u0947\u0915\u094d\u091f \u092f\u0942\u0906\u0930\u0906\u0908 \u0913\u0925 \u0910\u092a \u0915\u0949\u0928\u094d\u092b\u093c\u093f\u0917\u0930\u0947\u0936\u0928 \u0938\u0947 \u092e\u0947\u0932 \u0928\u0939\u0940\u0902 \u0916\u093e \u0930\u0939\u093e \u0939\u0948\u0964", + "oauthcallbackerror": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "oauthcreateaccount": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "emailcreateaccount": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "callback": "\u0915\u093f\u0938\u0940 \u0926\u0942\u0938\u0930\u0947 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0915\u0947 \u0926\u0947\u0916\u0947\u0902.", + "oauthaccountnotlinked": "\u0905\u092a\u0928\u0940 \u092a\u0939\u091a\u093e\u0928 \u0915\u0928\u094d\u092b\u093c\u0930\u094d\u092e \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f, \u0909\u0938\u0940 \u0916\u093e\u0924\u0947 \u0938\u0947 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0947\u0902 \u091c\u093f\u0938\u0915\u093e \u0907\u0938\u094d\u0924\u0947\u092e\u093e\u0932 \u0906\u092a\u0928\u0947 \u092a\u0939\u0932\u0947 \u0915\u093f\u092f\u093e \u0925\u093e.", + "emailsignin": "\u0908-\u092e\u0947\u0932 \u0928\u0939\u0940\u0902 \u092d\u0947\u091c\u0940 \u091c\u093e \u0938\u0915\u0940.", + "emailverify": "\u0915\u0943\u092a\u092f\u093e \u0905\u092a\u0928\u093e \u0908\u092e\u0947\u0932 \u0938\u0924\u094d\u092f\u093e\u092a\u093f\u0924 \u0915\u0930\u0947\u0902, \u090f\u0915 \u0928\u092f\u093e \u0908\u092e\u0947\u0932 \u092d\u0947\u091c\u093e \u0917\u092f\u093e \u0939\u0948\u0964", + "credentialssignin": "\u0938\u093e\u0907\u0928 \u0907\u0928 \u0935\u093f\u092b\u0932 \u0930\u0939\u093e. \u091c\u093e\u0902\u091a\u0947\u0902 \u0915\u093f \u0906\u092a\u0915\u0947 \u0926\u094d\u0935\u093e\u0930\u093e \u092a\u094d\u0930\u0926\u093e\u0928 \u0915\u093f\u090f \u0917\u090f \u0935\u093f\u0935\u0930\u0923 \u0938\u0939\u0940 \u0939\u0948\u0902\u0964", + "sessionrequired": "\u0915\u0943\u092a\u092f\u093e \u0907\u0938 \u092a\u0943\u0937\u094d\u0920 \u0924\u0915 \u092a\u0939\u0941\u0902\u091a\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0947\u0902\u0964" + } + }, + "authVerifyEmail": { + "almostThere": "\u0906\u092a \u0932\u0917\u092d\u0917 \u0935\u0939\u093e\u0901 \u0939\u0948\u0902! \u0939\u092e\u0928\u0947 \u090f\u0915 \u0908\u092e\u0947\u0932 \u092d\u0947\u091c\u093e \u0939\u0948 ", + "verifyEmailLink": "\u0915\u0943\u092a\u092f\u093e \u0905\u092a\u0928\u093e \u0938\u093e\u0907\u0928\u0905\u092a \u092a\u0942\u0930\u093e \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u0909\u0938 \u0908\u092e\u0947\u0932 \u092e\u0947\u0902 \u0926\u093f\u090f \u0917\u090f \u0932\u093f\u0902\u0915 \u092a\u0930 \u0915\u094d\u0932\u093f\u0915 \u0915\u0930\u0947\u0902\u0964", + "didNotReceive": "\u0908\u092e\u0947\u0932 \u0928\u0939\u0940\u0902 \u092e\u093f\u0932 \u0930\u0939\u093e \u0939\u0948?", + "resendEmail": "\u0908\u092e\u0947\u0932 \u092a\u0941\u0928\u0903 \u092d\u0947\u091c\u0947\u0902", + "goBack": "\u092a\u0938 \u091c\u093e\u0913", + "emailSent": "\u0908\u092e\u0947\u0932 \u0938\u092b\u0932\u0924\u093e\u092a\u0942\u0930\u094d\u0935\u0915 \u092d\u0947\u091c\u093e \u0917\u092f\u093e\u0964", + "verifyEmail": "\u0905\u092a\u0928\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u093e \u0938\u0924\u094d\u092f\u093e\u092a\u093f\u0924 \u0915\u0930\u0947\u0902" + }, + "providerButton": { + "continue": "{{provider}} \u0915\u0947 \u0938\u093e\u0925 \u091c\u093e\u0930\u0940 \u0930\u0916\u0947\u0902", + "signup": "{{provider}} \u0915\u0947 \u0938\u093e\u0925 \u0938\u093e\u0907\u0928 \u0905\u092a \u0915\u0930\u0947\u0902" + }, + "authResetPassword": { + "newPasswordRequired": "\u0928\u092f\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u092b\u093c\u0940\u0932\u094d\u0921 \u0939\u0948", + "passwordsMustMatch": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u092e\u0947\u0932 \u0916\u093e\u0928\u093e \u091a\u093e\u0939\u093f\u090f", + "confirmPasswordRequired": "\u092a\u0941\u0937\u094d\u091f\u093f \u0915\u0930\u0947\u0902 \u0915\u093f \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u092b\u093c\u0940\u0932\u094d\u0921 \u0939\u0948", + "newPassword": "\u0928\u092f\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921", + "confirmPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0915\u0940 \u092a\u0941\u0937\u094d\u091f\u093f \u0915\u0930\u0947\u0902", + "resetPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u0947\u0902" + }, + "authForgotPassword": { + "email": "\u0908\u092e\u0947\u0932 \u092a\u0924\u093e", + "emailRequired": "\u0908\u092e\u0947\u0932 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u092b\u093c\u0940\u0932\u094d\u0921 \u0939\u0948", + "emailSent": "\u0905\u092a\u0928\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u0928\u0947 \u0915\u0947 \u0928\u093f\u0930\u094d\u0926\u0947\u0936\u094b\u0902 \u0915\u0947 \u0932\u093f\u090f \u0915\u0943\u092a\u092f\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u093e {{email}} \u0926\u0947\u0916\u0947\u0902\u0964", + "enterEmail": "\u0905\u092a\u0928\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u093e \u0926\u0930\u094d\u091c \u0915\u0930\u0947\u0902 \u0914\u0930 \u0939\u092e \u0906\u092a\u0915\u094b \u0905\u092a\u0928\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u0928\u093f\u0930\u094d\u0926\u0947\u0936 \u092d\u0947\u091c\u0947\u0902\u0917\u0947\u0964", + "resendEmail": "\u0908\u092e\u0947\u0932 \u092a\u0941\u0928\u0903 \u092d\u0947\u091c\u0947\u0902", + "continue": "\u091c\u093e\u0930\u0940 \u0930\u0916\u0928\u093e", + "goBack": "\u092a\u0938 \u091c\u093e\u0913" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0907\u0924\u093f\u0939\u093e\u0938 \u0926\u093f\u0916\u093e\u090f\u0902", + "lastInputs": "\u0905\u0902\u0924\u093f\u092e \u0907\u0928\u092a\u0941\u091f", + "noInputs": "\u0910\u0938\u0947 \u0916\u093e\u0932\u0940...", + "loading": "\u0932\u094b\u0921\u0964\u0964\u0964" + } + }, + "inputBox": { + "input": { + "placeholder": "\u0905\u092a\u0928\u093e \u0938\u0902\u0926\u0947\u0936 \u092f\u0939\u093e\u0901 \u091f\u093e\u0907\u092a \u0915\u0930\u0947\u0902..." + }, + "speechButton": { + "start": "\u0930\u093f\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902", + "stop": "\u0930\u093f\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917 \u092c\u0902\u0926 \u0915\u0930\u094b" + }, + "SubmitButton": { + "sendMessage": "\u0938\u0902\u0926\u0947\u0936 \u092d\u0947\u091c\u0947\u0902", + "stopTask": "\u0915\u093e\u0930\u094d\u092f \u092c\u0902\u0926 \u0915\u0930\u094b" + }, + "UploadButton": { + "attachFiles": "\u092b\u093c\u093e\u0907\u0932\u0947\u0902 \u0905\u0928\u0941\u0932\u0917\u094d\u0928 \u0915\u0930\u0947\u0902" + }, + "waterMark": { + "text": "\u0915\u0947 \u0938\u093e\u0925 \u0928\u093f\u0930\u094d\u092e\u093f\u0924" + } + }, + "Messages": { + "index": { + "running": "\u092d\u093e\u0917\u0928\u093e", + "executedSuccessfully": "\u0938\u092b\u0932\u0924\u093e\u092a\u0942\u0930\u094d\u0935\u0915 \u0928\u093f\u0937\u094d\u092a\u093e\u0926\u093f\u0924", + "failed": "\u0905\u0938\u092b\u0932", + "feedbackUpdated": "\u092a\u094d\u0930\u0924\u093f\u0915\u094d\u0930\u093f\u092f\u093e \u0905\u092a\u0921\u0947\u091f \u0915\u0940 \u0917\u0908", + "updating": "\u0905\u0926\u094d\u092f\u0924\u0928" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0905\u092a\u0928\u0940 \u092b\u093c\u093e\u0907\u0932\u0947\u0902 \u092f\u0939\u093e\u0901 \u0921\u094d\u0930\u0949\u092a \u0915\u0930\u0947\u0902" + }, + "index": { + "failedToUpload": "\u0905\u092a\u0932\u094b\u0921 \u0915\u0930\u0928\u0947 \u092e\u0947\u0902 \u0935\u093f\u092b\u0932", + "cancelledUploadOf": "\u0915\u093e \u0905\u092a\u0932\u094b\u0921 \u0930\u0926\u094d\u0926 \u0915\u093f\u092f\u093e \u0917\u092f\u093e", + "couldNotReachServer": "\u0938\u0930\u094d\u0935\u0930 \u0924\u0915 \u0928\u0939\u0940\u0902 \u092a\u0939\u0941\u0901\u091a \u0938\u0915\u093e", + "continuingChat": "\u092a\u093f\u091b\u0932\u0940 \u091a\u0948\u091f \u091c\u093e\u0930\u0940 \u0930\u0916\u0928\u093e" + }, + "settings": { + "settingsPanel": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938 \u092a\u0948\u0928\u0932", + "reset": "\u0930\u0940\u0938\u0947\u091f", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u0928\u093e", + "confirm": "\u0938\u0941\u0926\u0943\u0922\u093c \u0915\u0930\u0928\u093e" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u092a\u094d\u0930\u0924\u093f\u092a\u0941\u0937\u094d\u091f\u093f: \u0938\u092d\u0940", + "feedbackPositive": "\u092a\u094d\u0930\u0924\u093f\u092a\u0941\u0937\u094d\u091f\u093f: \u0938\u0915\u093e\u0930\u093e\u0924\u094d\u092e\u0915", + "feedbackNegative": "\u092a\u094d\u0930\u0924\u093f\u092a\u0941\u0937\u094d\u091f\u093f: \u0928\u0915\u093e\u0930\u093e\u0924\u094d\u092e\u0915" + }, + "SearchBar": { + "search": "\u0922\u0942\u0901\u0922" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u092f\u0939 \u0925\u094d\u0930\u0947\u0921 \u0915\u0947 \u0938\u093e\u0925-\u0938\u093e\u0925 \u0907\u0938\u0915\u0947 \u0938\u0902\u0926\u0947\u0936\u094b\u0902 \u0914\u0930 \u0924\u0924\u094d\u0935\u094b\u0902 \u0915\u094b \u092d\u0940 \u0939\u091f\u093e \u0926\u0947\u0917\u093e\u0964", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u0928\u093e", + "confirm": "\u0938\u0941\u0926\u0943\u0922\u093c \u0915\u0930\u0928\u093e", + "deletingChat": "\u091a\u0948\u091f \u0939\u091f\u093e\u0928\u093e", + "chatDeleted": "\u091a\u0948\u091f \u0939\u091f\u093e\u0908 \u0917\u0908" + }, + "index": { + "pastChats": "\u092a\u093f\u091b\u0932\u0940 \u091a\u0948\u091f" + }, + "ThreadList": { + "empty": "\u0916\u093e\u0932\u0940\u0964\u0964\u0964", + "today": "\u0906\u091c", + "yesterday": "\u092c\u0940\u0924\u093e \u0939\u0941\u0906 \u0915\u0932", + "previous7days": "\u092a\u093f\u091b\u0932\u0947 7 \u0926\u093f\u0928", + "previous30days": "\u092a\u093f\u091b\u0932\u0947 30 \u0926\u093f\u0928" + }, + "TriggerButton": { + "closeSidebar": "\u0938\u093e\u0907\u0921\u092c\u093e\u0930 \u092c\u0902\u0926 \u0915\u0930\u0947\u0902", + "openSidebar": "\u0938\u093e\u0907\u0921\u092c\u093e\u0930 \u0916\u094b\u0932\u0947\u0902" + } + }, + "Thread": { + "backToChat": "\u091a\u0948\u091f \u092a\u0930 \u0935\u093e\u092a\u0938 \u091c\u093e\u090f\u0902", + "chatCreatedOn": "\u092f\u0939 \u091a\u0948\u091f \u0907\u0938 \u092a\u0930 \u092c\u0928\u093e\u0908 \u0917\u0908 \u0925\u0940" + } + }, + "header": { + "chat": "\u091a\u0948\u091f", + "readme": "\u0930\u0940\u0921\u092e\u0940" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u092a\u094d\u0930\u0926\u093e\u0924\u093e\u0913\u0902 \u0915\u094b \u0932\u093e\u0928\u0947 \u092e\u0947\u0902 \u0935\u093f\u092b\u0932:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0938\u092b\u0932\u0924\u093e\u092a\u0942\u0930\u094d\u0935\u0915 \u0938\u0939\u0947\u091c\u093e \u0917\u092f\u093e", + "requiredApiKeys": "\u0906\u0935\u0936\u094d\u092f\u0915 \u090f\u092a\u0940\u0906\u0908 \u0915\u0941\u0902\u091c\u0940", + "requiredApiKeysInfo": "\u0907\u0938 \u0910\u092a \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f, \u0928\u093f\u092e\u094d\u0928\u0932\u093f\u0916\u093f\u0924 \u090f\u092a\u0940\u0906\u0908 \u0915\u0941\u0902\u091c\u093f\u092f\u094b\u0902 \u0915\u0940 \u0906\u0935\u0936\u094d\u092f\u0915\u0924\u093e \u0939\u094b\u0924\u0940 \u0939\u0948\u0964 \u091a\u093e\u092c\u093f\u092f\u093e\u0901 \u0906\u092a\u0915\u0947 \u0921\u093f\u0935\u093e\u0907\u0938 \u0915\u0947 \u0938\u094d\u0925\u093e\u0928\u0940\u092f \u0938\u0902\u0917\u094d\u0930\u0939\u0923 \u092a\u0930 \u0938\u0902\u0917\u094d\u0930\u0939\u0940\u0924 \u0915\u0940 \u091c\u093e\u0924\u0940 \u0939\u0948\u0902\u0964" + }, + "Page": { + "notPartOfProject": "\u0906\u092a \u0907\u0938 \u092a\u0930\u093f\u092f\u094b\u091c\u0928\u093e \u0915\u093e \u0939\u093f\u0938\u094d\u0938\u093e \u0928\u0939\u0940\u0902 \u0939\u0948\u0902\u0964" + }, + "ResumeButton": { + "resumeChat": "\u091a\u0948\u091f \u092b\u093f\u0930 \u0938\u0947 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/kn.json b/examples/chatbot/.chainlit/translations/kn.json new file mode 100644 index 000000000..d09db5f90 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/kn.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0cb8\u0cc6\u0c9f\u0ccd\u0c9f\u0cbf\u0c82\u0c97\u0ccd \u0c97\u0cb3\u0cc1", + "settingsKey": "S", + "APIKeys": "API \u0c95\u0cc0\u0cb2\u0cbf\u0c97\u0cb3\u0cc1", + "logout": "\u0cb2\u0cbe\u0c97\u0ccd \u0c94\u0c9f\u0ccd" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0cb9\u0cca\u0cb8 \u0c9a\u0cbe\u0c9f\u0ccd" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0c95\u0cbe\u0cb0\u0ccd\u0caf \u0caa\u0c9f\u0ccd\u0c9f\u0cbf", + "loading": "\u0cb2\u0ccb\u0ca1\u0ccd \u0c86\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6...", + "error": "\u0ca6\u0ccb\u0cb7 \u0cb8\u0c82\u0cad\u0cb5\u0cbf\u0cb8\u0cbf\u0ca6\u0cc6" + } + }, + "attachments": { + "cancelUpload": "\u0c85\u0caa\u0ccd \u0cb2\u0ccb\u0ca1\u0ccd \u0cb0\u0ca6\u0ccd\u0ca6\u0cc1 \u0cae\u0cbe\u0ca1\u0cbf", + "removeAttachment": "\u0cb2\u0c97\u0ca4\u0ccd\u0ca4\u0cc1 \u0ca4\u0cc6\u0c97\u0cc6\u0ca6\u0cc1\u0cb9\u0cbe\u0c95\u0cbf" + }, + "newChatDialog": { + "createNewChat": "\u0cb9\u0cca\u0cb8 \u0c9a\u0cbe\u0c9f\u0ccd \u0cb0\u0c9a\u0cbf\u0cb8\u0cac\u0cc7\u0c95\u0cc6?", + "clearChat": "\u0c87\u0ca6\u0cc1 \u0caa\u0ccd\u0cb0\u0cb8\u0ccd\u0ca4\u0cc1\u0ca4 \u0cb8\u0c82\u0ca6\u0cc7\u0cb6\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0ca4\u0cc6\u0cb0\u0cb5\u0cc1\u0c97\u0cca\u0cb3\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0ca6\u0cc6 \u0cae\u0ca4\u0ccd\u0ca4\u0cc1 \u0cb9\u0cca\u0cb8 \u0c9a\u0cbe\u0c9f\u0ccd \u0c85\u0ca8\u0ccd\u0ca8\u0cc1 \u0caa\u0ccd\u0cb0\u0cbe\u0cb0\u0c82\u0cad\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0ca6\u0cc6.", + "cancel": "\u0cb0\u0ca6\u0ccd\u0ca6\u0cc1\u0cae\u0cbe\u0ca1\u0cbf", + "confirm": "\u0ca6\u0cc3\u0ca2\u0caa\u0ca1\u0cbf\u0cb8\u0cbf" + }, + "settingsModal": { + "settings": "\u0cb8\u0cc6\u0c9f\u0ccd\u0c9f\u0cbf\u0c82\u0c97\u0ccd \u0c97\u0cb3\u0cc1", + "expandMessages": "\u0cb8\u0c82\u0ca6\u0cc7\u0cb6\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0cb5\u0cbf\u0cb8\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cbf", + "hideChainOfThought": "\u0c9a\u0cbf\u0c82\u0ca4\u0ca8\u0cc6\u0caf \u0cb8\u0cb0\u0caa\u0cb3\u0cbf\u0caf\u0ca8\u0ccd\u0ca8\u0cc1 \u0cae\u0cb0\u0cc6\u0cae\u0cbe\u0ca1\u0cc1", + "darkMode": "\u0ca1\u0cbe\u0cb0\u0ccd\u0c95\u0ccd \u0cae\u0ccb\u0ca1\u0ccd" + }, + "detailsButton": { + "using": "\u0cac\u0cb3\u0cb8\u0cb2\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6", + "running": "\u0c9a\u0cb2\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6", + "took_one": "{{count}} \u0cb9\u0cc6\u0c9c\u0ccd\u0c9c\u0cc6 \u0c87\u0c9f\u0ccd\u0c9f\u0cbf\u0ca6\u0cc6", + "took_other": "{{count}} \u0cb9\u0cc6\u0c9c\u0ccd\u0c9c\u0cc6\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0ca4\u0cc6\u0c97\u0cc6\u0ca6\u0cc1\u0c95\u0cca\u0c82\u0ca1\u0cb0\u0cc1" + }, + "auth": { + "authLogin": { + "title": "\u0c85\u0caa\u0ccd\u0cb2\u0cbf\u0c95\u0cc7\u0cb6\u0ca8\u0ccd \u0caa\u0ccd\u0cb0\u0cb5\u0cc7\u0cb6\u0cbf\u0cb8\u0cb2\u0cc1 \u0cb2\u0cbe\u0c97\u0cbf\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cbf.", + "form": { + "email": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb5\u0cbf\u0cb3\u0cbe\u0cb8", + "password": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd", + "noAccount": "\u0c96\u0cbe\u0ca4\u0cc6 \u0c87\u0cb2\u0ccd\u0cb2\u0cb5\u0cc7?", + "alreadyHaveAccount": "\u0c88\u0c97\u0cbe\u0c97\u0cb2\u0cc7 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0ca8\u0ccd\u0ca8\u0cc1 \u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0ca6\u0ccd\u0ca6\u0cc0\u0cb0\u0cbe?", + "signup": "\u0cb8\u0cc8\u0ca8\u0ccd \u0c85\u0caa\u0ccd \u0cae\u0cbe\u0ca1\u0cbf", + "signin": "\u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cbf", + "or": "\u0c85\u0ca5\u0cb5\u0cbe", + "continue": "\u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0cb8\u0cbf", + "forgotPassword": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0cae\u0cb0\u0cc6\u0ca4\u0cbf\u0ca6\u0ccd\u0ca6\u0cc0\u0cb0\u0cbe?", + "passwordMustContain": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0c87\u0cb5\u0cc1\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0c92\u0cb3\u0c97\u0cca\u0c82\u0ca1\u0cbf\u0cb0\u0cac\u0cc7\u0c95\u0cc1:", + "emailRequired": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 \u0cab\u0cc0\u0cb2\u0ccd\u0ca1\u0ccd \u0c86\u0c97\u0cbf\u0ca6\u0cc6", + "passwordRequired": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 \u0cab\u0cc0\u0cb2\u0ccd\u0ca1\u0ccd \u0c86\u0c97\u0cbf\u0ca6\u0cc6" + }, + "error": { + "default": "\u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0c85\u0cb8\u0cae\u0cb0\u0ccd\u0ca5\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6.", + "signin": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "oauthsignin": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "redirect_uri_mismatch": "\u0cae\u0cb0\u0cc1\u0ca8\u0cbf\u0cb0\u0ccd\u0ca6\u0cc7\u0cb6\u0ca8\u0ca6 URI \u0c86\u0ccd\u0caf\u0caa\u0ccd \u0c95\u0cbe\u0ca8\u0ccd\u0cab\u0cbf\u0c97\u0cb0\u0cc7\u0cb6\u0ca8\u0ccd \u0c97\u0cc6 \u0cb9\u0ccb\u0cb2\u0cbf\u0c95\u0cc6\u0caf\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0cb2\u0ccd\u0cb2.", + "oauthcallbackerror": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "oauthcreateaccount": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "emailcreateaccount": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "callback": "\u0cac\u0cc7\u0cb0\u0cc6 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.", + "oauthaccountnotlinked": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0c97\u0cc1\u0cb0\u0cc1\u0ca4\u0ca8\u0ccd\u0ca8\u0cc1 \u0ca6\u0cc3\u0ca2\u0cc0\u0c95\u0cb0\u0cbf\u0cb8\u0cb2\u0cc1, \u0ca8\u0cc0\u0cb5\u0cc1 \u0cae\u0cc2\u0cb2\u0ca4\u0c83 \u0cac\u0cb3\u0cb8\u0cbf\u0ca6 \u0c85\u0ca6\u0cc7 \u0c96\u0cbe\u0ca4\u0cc6\u0caf\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cbf.", + "emailsignin": "\u0c87-\u0cae\u0cc7\u0cb2\u0ccd \u0c95\u0cb3\u0cc1\u0cb9\u0cbf\u0cb8\u0cb2\u0cc1 \u0cb8\u0cbe\u0ca7\u0ccd\u0caf\u0cb5\u0cbe\u0c97\u0cb2\u0cbf\u0cb2\u0ccd\u0cb2.", + "emailverify": "\u0ca6\u0caf\u0cb5\u0cbf\u0c9f\u0ccd\u0c9f\u0cc1 \u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0caa\u0cb0\u0cbf\u0cb6\u0cc0\u0cb2\u0cbf\u0cb8\u0cbf, \u0cb9\u0cca\u0cb8 \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c95\u0cb3\u0cc1\u0cb9\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6.", + "credentialssignin": "\u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cb5\u0cbf\u0cab\u0cb2\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6. \u0ca8\u0cc0\u0cb5\u0cc1 \u0c92\u0ca6\u0c97\u0cbf\u0cb8\u0cbf\u0ca6 \u0cb5\u0cbf\u0cb5\u0cb0\u0c97\u0cb3\u0cc1 \u0cb8\u0cb0\u0cbf\u0caf\u0cbe\u0c97\u0cbf\u0cb5\u0cc6\u0caf\u0cc7 \u0c8e\u0c82\u0ca6\u0cc1 \u0caa\u0cb0\u0cbf\u0cb6\u0cc0\u0cb2\u0cbf\u0cb8\u0cbf.", + "sessionrequired": "\u0c88 \u0caa\u0cc1\u0c9f\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1 \u0caa\u0ccd\u0cb0\u0cb5\u0cc7\u0cb6\u0cbf\u0cb8\u0cb2\u0cc1 \u0ca6\u0caf\u0cb5\u0cbf\u0c9f\u0ccd\u0c9f\u0cc1 \u0cb8\u0cc8\u0ca8\u0ccd \u0c87\u0ca8\u0ccd \u0cae\u0cbe\u0ca1\u0cbf." + } + }, + "authVerifyEmail": { + "almostThere": "\u0ca8\u0cc0\u0cb5\u0cc1 \u0cac\u0cb9\u0cc1\u0ca4\u0cc7\u0c95 \u0c85\u0cb2\u0ccd\u0cb2\u0cbf\u0ca6\u0ccd\u0ca6\u0cc0\u0cb0\u0cbf! \u0ca8\u0cbe\u0cb5\u0cc1 \u0c97\u0cc6 \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c95\u0cb3\u0cc1\u0cb9\u0cbf\u0cb8\u0cbf\u0ca6\u0ccd\u0ca6\u0cc7\u0cb5\u0cc6 ", + "verifyEmailLink": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0cb8\u0cc8\u0ca8\u0ccd \u0c85\u0caa\u0ccd \u0caa\u0cc2\u0cb0\u0ccd\u0ca3\u0c97\u0cca\u0cb3\u0cbf\u0cb8\u0cb2\u0cc1 \u0ca6\u0caf\u0cb5\u0cbf\u0c9f\u0ccd\u0c9f\u0cc1 \u0c86 \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0ca8\u0cb2\u0ccd\u0cb2\u0cbf\u0cb0\u0cc1\u0cb5 \u0cb2\u0cbf\u0c82\u0c95\u0ccd \u0c95\u0ccd\u0cb2\u0cbf\u0c95\u0ccd \u0cae\u0cbe\u0ca1\u0cbf.", + "didNotReceive": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb9\u0cc1\u0ca1\u0cc1\u0c95\u0cb2\u0cc1 \u0cb8\u0cbe\u0ca7\u0ccd\u0caf\u0cb5\u0cbf\u0cb2\u0ccd\u0cb2\u0cb5\u0cc7?", + "resendEmail": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c85\u0ca8\u0ccd\u0ca8\u0cc1 \u0cae\u0ca4\u0ccd\u0ca4\u0cc6 \u0c95\u0cb3\u0cbf\u0cb8\u0cbf", + "goBack": "\u0cb9\u0cbf\u0c82\u0ca6\u0cc6 \u0cb9\u0cc6\u0cc2\u0cd5\u0c97\u0cc1", + "emailSent": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0caf\u0cb6\u0cb8\u0ccd\u0cb5\u0cbf\u0caf\u0cbe\u0c97\u0cbf \u0c95\u0cb3\u0cc1\u0cb9\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6.", + "verifyEmail": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb5\u0cbf\u0cb3\u0cbe\u0cb8\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1 \u0caa\u0cb0\u0cbf\u0cb6\u0cc0\u0cb2\u0cbf\u0cb8\u0cbf" + }, + "providerButton": { + "continue": "{{provider}} \u0ca8\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0cb8\u0cbf", + "signup": "{{provider}} \u0ca8\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0cb8\u0cc8\u0ca8\u0ccd \u0c85\u0caa\u0ccd \u0cae\u0cbe\u0ca1\u0cbf" + }, + "authResetPassword": { + "newPasswordRequired": "\u0cb9\u0cca\u0cb8 \u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 \u0cab\u0cc0\u0cb2\u0ccd\u0ca1\u0ccd \u0c86\u0c97\u0cbf\u0ca6\u0cc6", + "passwordsMustMatch": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0c97\u0cb3\u0cc1 \u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0c95\u0cc6\u0caf\u0cbe\u0c97\u0cac\u0cc7\u0c95\u0cc1", + "confirmPasswordRequired": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 \u0cab\u0cc0\u0cb2\u0ccd\u0ca1\u0ccd \u0c8e\u0c82\u0ca6\u0cc1 \u0ca6\u0cc3\u0ca2\u0caa\u0ca1\u0cbf\u0cb8\u0cbf", + "newPassword": "\u0cb9\u0cca\u0cb8 \u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd", + "confirmPassword": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0ca6\u0cc3\u0ca2\u0caa\u0ca1\u0cbf\u0cb8\u0cbf", + "resetPassword": "\u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0cae\u0cb0\u0cc1\u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0cb8\u0cbf" + }, + "authForgotPassword": { + "email": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb5\u0cbf\u0cb3\u0cbe\u0cb8", + "emailRequired": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 \u0cab\u0cc0\u0cb2\u0ccd\u0ca1\u0ccd \u0c86\u0c97\u0cbf\u0ca6\u0cc6", + "emailSent": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0cae\u0cb0\u0cc1\u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0cb8\u0cb2\u0cc1 \u0cb8\u0cc2\u0c9a\u0ca8\u0cc6\u0c97\u0cb3\u0cbf\u0c97\u0cbe\u0c97\u0cbf \u0ca6\u0caf\u0cb5\u0cbf\u0c9f\u0ccd\u0c9f\u0cc1 \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb5\u0cbf\u0cb3\u0cbe\u0cb8 {{email}} \u0caa\u0cb0\u0cbf\u0cb6\u0cc0\u0cb2\u0cbf\u0cb8\u0cbf.", + "enterEmail": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0cb5\u0cbf\u0cb3\u0cbe\u0cb8\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1 \u0ca8\u0cae\u0cc2\u0ca6\u0cbf\u0cb8\u0cbf \u0cae\u0ca4\u0ccd\u0ca4\u0cc1 \u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0caa\u0cbe\u0cb8\u0ccd \u0cb5\u0cb0\u0ccd\u0ca1\u0ccd \u0cae\u0cb0\u0cc1\u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0cb8\u0cb2\u0cc1 \u0ca8\u0cbe\u0cb5\u0cc1 \u0ca8\u0cbf\u0cae\u0c97\u0cc6 \u0cb8\u0cc2\u0c9a\u0ca8\u0cc6\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0c95\u0cb3\u0cc1\u0cb9\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0cc7\u0cb5\u0cc6.", + "resendEmail": "\u0c87\u0cae\u0cc7\u0cb2\u0ccd \u0c85\u0ca8\u0ccd\u0ca8\u0cc1 \u0cae\u0ca4\u0ccd\u0ca4\u0cc6 \u0c95\u0cb3\u0cbf\u0cb8\u0cbf", + "continue": "\u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0cb8\u0cbf", + "goBack": "\u0cb9\u0cbf\u0c82\u0ca6\u0cc6 \u0cb9\u0cc6\u0cc2\u0cd5\u0c97\u0cc1" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0c87\u0ca4\u0cbf\u0cb9\u0cbe\u0cb8 \u0ca4\u0ccb\u0cb0\u0cbf\u0cb8\u0cc1", + "lastInputs": "\u0c95\u0cca\u0ca8\u0cc6\u0caf \u0c87\u0ca8\u0ccd \u0caa\u0cc1\u0c9f\u0ccd \u0c97\u0cb3\u0cc1", + "noInputs": "\u0c8e\u0c82\u0ca4\u0cb9 \u0c96\u0cbe\u0cb2\u0cbf...", + "loading": "\u0cb2\u0ccb\u0ca1\u0ccd \u0c86\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0cb8\u0c82\u0ca6\u0cc7\u0cb6\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1 \u0c87\u0cb2\u0ccd\u0cb2\u0cbf \u0cac\u0cc6\u0cb0\u0cb3\u0c9a\u0ccd\u0c9a\u0cbf\u0cb8\u0cbf..." + }, + "speechButton": { + "start": "\u0cb0\u0cc6\u0c95\u0cbe\u0cb0\u0ccd\u0ca1\u0cbf\u0c82\u0c97\u0ccd \u0caa\u0ccd\u0cb0\u0cbe\u0cb0\u0c82\u0cad\u0cbf\u0cb8\u0cbf", + "stop": "\u0cb0\u0cc6\u0c95\u0cbe\u0cb0\u0ccd\u0ca1\u0cbf\u0c82\u0c97\u0ccd \u0ca8\u0cbf\u0cb2\u0ccd\u0cb2\u0cbf\u0cb8\u0cc1" + }, + "SubmitButton": { + "sendMessage": "\u0cb8\u0c82\u0ca6\u0cc7\u0cb6 \u0c95\u0cb3\u0cbf\u0cb8\u0cbf", + "stopTask": "\u0ca8\u0cbf\u0cb2\u0ccd\u0cb2\u0cbf\u0cb8\u0cc1 \u0c95\u0cbe\u0cb0\u0ccd\u0caf" + }, + "UploadButton": { + "attachFiles": "\u0cab\u0cc8\u0cb2\u0ccd \u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0cb2\u0c97\u0ca4\u0ccd\u0ca4\u0cbf\u0cb8\u0cbf" + }, + "waterMark": { + "text": "\u0c87\u0ca6\u0cb0\u0cca\u0c82\u0ca6\u0cbf\u0c97\u0cc6 \u0ca8\u0cbf\u0cb0\u0ccd\u0cae\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6" + } + }, + "Messages": { + "index": { + "running": "\u0c9a\u0cb2\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6", + "executedSuccessfully": "\u0caf\u0cb6\u0cb8\u0ccd\u0cb5\u0cbf\u0caf\u0cbe\u0c97\u0cbf \u0c95\u0cbe\u0cb0\u0ccd\u0caf\u0c97\u0ca4\u0c97\u0cca\u0cb3\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "failed": "\u0cb5\u0cbf\u0cab\u0cb2\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "feedbackUpdated": "\u0caa\u0ccd\u0cb0\u0ca4\u0cbf\u0c95\u0ccd\u0cb0\u0cbf\u0caf\u0cc6 \u0ca8\u0cb5\u0cc0\u0c95\u0cb0\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "updating": "\u0ca8\u0cb5\u0cc0\u0c95\u0cb0\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0cab\u0cc8\u0cb2\u0ccd \u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0c87\u0cb2\u0ccd\u0cb2\u0cbf \u0cac\u0cbf\u0ca1\u0cbf" + }, + "index": { + "failedToUpload": "\u0c85\u0caa\u0ccd \u0cb2\u0ccb\u0ca1\u0ccd \u0cae\u0cbe\u0ca1\u0cb2\u0cc1 \u0cb5\u0cbf\u0cab\u0cb2\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "cancelledUploadOf": "\u0c85\u0caa\u0ccd \u0cb2\u0ccb\u0ca1\u0ccd \u0cb0\u0ca6\u0ccd\u0ca6\u0cc1\u0c97\u0cca\u0c82\u0ca1\u0cbf\u0ca6\u0cc6", + "couldNotReachServer": "\u0cb8\u0cb0\u0ccd\u0cb5\u0cb0\u0ccd \u0ca4\u0cb2\u0cc1\u0caa\u0cb2\u0cc1 \u0cb8\u0cbe\u0ca7\u0ccd\u0caf\u0cb5\u0cbe\u0c97\u0cb2\u0cbf\u0cb2\u0ccd\u0cb2", + "continuingChat": "\u0cb9\u0cbf\u0c82\u0ca6\u0cbf\u0ca8 \u0c9a\u0cbe\u0c9f\u0ccd \u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0caf\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6" + }, + "settings": { + "settingsPanel": "\u0cb8\u0cc6\u0c9f\u0ccd\u0c9f\u0cbf\u0c82\u0c97\u0ccd \u0c97\u0cb3 \u0cab\u0cb2\u0c95", + "reset": "\u0cae\u0cb0\u0cc1\u0cb9\u0cca\u0c82\u0ca6\u0cbf\u0cb8\u0cbf", + "cancel": "\u0cb0\u0ca6\u0ccd\u0ca6\u0cc1\u0cae\u0cbe\u0ca1\u0cbf", + "confirm": "\u0ca6\u0cc3\u0ca2\u0caa\u0ca1\u0cbf\u0cb8\u0cbf" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u0caa\u0ccd\u0cb0\u0ca4\u0cbf\u0c95\u0ccd\u0cb0\u0cbf\u0caf\u0cc6: \u0c8e\u0cb2\u0ccd\u0cb2\u0cb5\u0cc2", + "feedbackPositive": "\u0caa\u0ccd\u0cb0\u0ca4\u0cbf\u0c95\u0ccd\u0cb0\u0cbf\u0caf\u0cc6: \u0ca7\u0ca8\u0cbe\u0ca4\u0ccd\u0cae\u0c95", + "feedbackNegative": "\u0caa\u0ccd\u0cb0\u0ca4\u0cbf\u0c95\u0ccd\u0cb0\u0cbf\u0caf\u0cc6: \u0ca8\u0c95\u0cbe\u0cb0\u0cbe\u0ca4\u0ccd\u0cae\u0c95" + }, + "SearchBar": { + "search": "\u0cb9\u0cc1\u0ca1\u0cc1\u0c95\u0cc1" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0c87\u0ca6\u0cc1 \u0ca5\u0ccd\u0cb0\u0cc6\u0ca1\u0ccd \u0cae\u0ca4\u0ccd\u0ca4\u0cc1 \u0c85\u0ca6\u0cb0 \u0cb8\u0c82\u0ca6\u0cc7\u0cb6\u0c97\u0cb3\u0cc1 \u0cae\u0ca4\u0ccd\u0ca4\u0cc1 \u0c85\u0c82\u0cb6\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0c85\u0cb3\u0cbf\u0cb8\u0cc1\u0ca4\u0ccd\u0ca4\u0ca6\u0cc6.", + "cancel": "\u0cb0\u0ca6\u0ccd\u0ca6\u0cc1\u0cae\u0cbe\u0ca1\u0cbf", + "confirm": "\u0ca6\u0cc3\u0ca2\u0caa\u0ca1\u0cbf\u0cb8\u0cbf", + "deletingChat": "\u0c9a\u0cbe\u0c9f\u0ccd \u0c85\u0cb3\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cbf\u0ca6\u0cc6", + "chatDeleted": "\u0c9a\u0cbe\u0c9f\u0ccd \u0c85\u0cb3\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6" + }, + "index": { + "pastChats": "\u0cb9\u0cbf\u0c82\u0ca6\u0cbf\u0ca8 \u0c9a\u0cbe\u0c9f\u0ccd \u0c97\u0cb3\u0cc1" + }, + "ThreadList": { + "empty": "\u0c96\u0cbe\u0cb2\u0cbf...", + "today": "\u0c87\u0c82\u0ca6\u0cc1", + "yesterday": "\u0ca8\u0cbf\u0ca8\u0ccd\u0ca8\u0cc6", + "previous7days": "\u0cb9\u0cbf\u0c82\u0ca6\u0cbf\u0ca8 7 \u0ca6\u0cbf\u0ca8\u0c97\u0cb3\u0cc1", + "previous30days": "\u0cb9\u0cbf\u0c82\u0ca6\u0cbf\u0ca8 30 \u0ca6\u0cbf\u0ca8\u0c97\u0cb3\u0cc1" + }, + "TriggerButton": { + "closeSidebar": "\u0cb8\u0cc8\u0ca1\u0ccd \u0cac\u0cbe\u0cb0\u0ccd \u0cae\u0cc1\u0c9a\u0ccd\u0c9a\u0cc1", + "openSidebar": "\u0cb8\u0cc8\u0ca1\u0ccd \u0cac\u0cbe\u0cb0\u0ccd \u0ca4\u0cc6\u0cb0\u0cc6\u0caf\u0cbf\u0cb0\u0cbf" + } + }, + "Thread": { + "backToChat": "\u0c9a\u0cbe\u0c9f\u0ccd \u0c97\u0cc6 \u0cb9\u0cbf\u0c82\u0ca4\u0cbf\u0cb0\u0cc1\u0c97\u0cbf", + "chatCreatedOn": "\u0c88 \u0c9a\u0cbe\u0c9f\u0ccd \u0c85\u0ca8\u0ccd\u0ca8\u0cc1 \u0c88 \u0ca8\u0cb2\u0ccd\u0cb2\u0cbf \u0cb0\u0c9a\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6" + } + }, + "header": { + "chat": "\u0c9a\u0cbe\u0c9f\u0ccd", + "readme": "Readme" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u0caa\u0cc2\u0cb0\u0cc8\u0c95\u0cc6\u0ca6\u0cbe\u0cb0\u0cb0\u0ca8\u0ccd\u0ca8\u0cc1 \u0c95\u0cb0\u0cc6\u0ca4\u0cb0\u0cb2\u0cc1 \u0cb5\u0cbf\u0cab\u0cb2\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0caf\u0cb6\u0cb8\u0ccd\u0cb5\u0cbf\u0caf\u0cbe\u0c97\u0cbf \u0c89\u0cb3\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "requiredApiKeys": "\u0c85\u0cb5\u0cb6\u0ccd\u0caf\u0c95 API \u0c95\u0cc0\u0cb2\u0cbf\u0c97\u0cb3\u0cc1", + "requiredApiKeysInfo": "\u0c88 \u0c85\u0caa\u0ccd\u0cb2\u0cbf\u0c95\u0cc7\u0cb6\u0ca8\u0ccd \u0c85\u0ca8\u0ccd\u0ca8\u0cc1 \u0cac\u0cb3\u0cb8\u0cb2\u0cc1, \u0c88 \u0c95\u0cc6\u0cb3\u0c97\u0cbf\u0ca8 \u0c8e\u0caa\u0cbf\u0c90 \u0c95\u0cc0\u0cb2\u0cbf\u0c97\u0cb3\u0cc1 \u0cac\u0cc7\u0c95\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0cb5\u0cc6. \u0c95\u0cc0\u0cb2\u0cbf\u0c97\u0cb3\u0ca8\u0ccd\u0ca8\u0cc1 \u0ca8\u0cbf\u0cae\u0ccd\u0cae \u0cb8\u0cbe\u0ca7\u0ca8\u0ca6 \u0cb8\u0ccd\u0ca5\u0cb3\u0cc0\u0caf \u0cb8\u0c82\u0c97\u0ccd\u0cb0\u0cb9\u0ca3\u0cc6\u0caf\u0cb2\u0ccd\u0cb2\u0cbf \u0cb8\u0c82\u0c97\u0ccd\u0cb0\u0cb9\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cc1\u0ca4\u0ccd\u0ca4\u0ca6\u0cc6." + }, + "Page": { + "notPartOfProject": "\u0ca8\u0cc0\u0cb5\u0cc1 \u0c88 \u0caf\u0ccb\u0c9c\u0ca8\u0cc6\u0caf \u0cad\u0cbe\u0c97\u0cb5\u0cbe\u0c97\u0cbf\u0cb2\u0ccd\u0cb2." + }, + "ResumeButton": { + "resumeChat": "\u0c9a\u0cbe\u0c9f\u0ccd \u0caa\u0cc1\u0ca8\u0cb0\u0cbe\u0cb0\u0c82\u0cad\u0cbf\u0cb8\u0cbf" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/ml.json b/examples/chatbot/.chainlit/translations/ml.json new file mode 100644 index 000000000..fca2fe077 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/ml.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d23\u0d19\u0d4d\u0d19\u0d7e", + "settingsKey": "S", + "APIKeys": "API \u0d15\u0d40\u0d15\u0d7e", + "logout": "\u0d32\u0d4b\u0d17\u0d4b\u0d1f\u0d4d\u0d1f\u0d4d" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0d1f\u0d3e\u0d38\u0d4d\u0d15\u0d4d \u0d32\u0d3f\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4d", + "loading": "\u0d32\u0d4b\u0d21\u0d3f\u0d02\u0d17\u0d4d...", + "error": "\u0d12\u0d30\u0d41 \u0d2a\u0d3f\u0d36\u0d15\u0d4d \u0d38\u0d02\u0d2d\u0d35\u0d3f\u0d1a\u0d4d\u0d1a\u0d41" + } + }, + "attachments": { + "cancelUpload": "\u0d05\u0d2a\u0d4d\u0d32\u0d4b\u0d21\u0d4d \u0d31\u0d26\u0d4d\u0d26\u0d3e\u0d15\u0d4d\u0d15\u0d41\u0d15", + "removeAttachment": "\u0d05\u0d31\u0d4d\u0d31\u0d3e\u0d1a\u0d4d\u0d1a\u0d4d \u0d2e\u0d46\u0d28\u0d4d\u0d31\u0d4d \u0d28\u0d40\u0d15\u0d4d\u0d15\u0d02\u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15" + }, + "newChatDialog": { + "createNewChat": "\u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d09\u0d23\u0d4d\u0d1f\u0d3e\u0d15\u0d4d\u0d15\u0d23\u0d4b?", + "clearChat": "\u0d07\u0d24\u0d4d \u0d28\u0d3f\u0d32\u0d35\u0d3f\u0d32\u0d46 \u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d7e \u0d15\u0d4d\u0d32\u0d3f\u0d2f\u0d7c \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15\u0d2f\u0d41\u0d02 \u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d06\u0d30\u0d02\u0d2d\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15\u0d2f\u0d41\u0d02 \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d02.", + "cancel": "\u0d15\u0d4d\u0d2f\u0d3e\u0d7b\u0d38\u0d7d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d4d", + "confirm": "\u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15" + }, + "settingsModal": { + "settings": "\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d23\u0d19\u0d4d\u0d19\u0d7e", + "expandMessages": "\u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d7e \u0d35\u0d3f\u0d15\u0d38\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "hideChainOfThought": "\u0d1a\u0d3f\u0d28\u0d4d\u0d24\u0d2f\u0d41\u0d1f\u0d46 \u0d36\u0d43\u0d02\u0d16\u0d32 \u0d2e\u0d31\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d15", + "darkMode": "\u0d21\u0d3e\u0d7c\u0d15\u0d4d\u0d15\u0d4d \u0d2e\u0d4b\u0d21\u0d4d" + }, + "detailsButton": { + "using": "\u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d02", + "running": "\u0d13\u0d1f\u0d41\u0d28\u0d4d\u0d28\u0d41", + "took_one": "{{count}} \u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d46\u0d2a\u0d4d\u0d2a\u0d4d \u0d0e\u0d1f\u0d41\u0d24\u0d4d\u0d24\u0d41", + "took_other": "{{count}} \u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d46\u0d2a\u0d4d\u0d2a\u0d41\u0d15\u0d7e \u0d0e\u0d1f\u0d41\u0d24\u0d4d\u0d24\u0d41" + }, + "auth": { + "authLogin": { + "title": "\u0d05\u0d2a\u0d4d\u0d32\u0d3f\u0d15\u0d4d\u0d15\u0d47\u0d37\u0d7b \u0d06\u0d15\u0d4d\u0d38\u0d38\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d32\u0d4b\u0d17\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15.", + "form": { + "email": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d32\u0d3e\u0d38\u0d02", + "password": "Password", + "noAccount": "\u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d07\u0d32\u0d4d\u0d32\u0d47?", + "alreadyHaveAccount": "\u0d12\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d23\u0d4d\u0d1f\u0d4b?", + "signup": "\u0d38\u0d48\u0d7b \u0d05\u0d2a\u0d4d\u0d2a\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", + "signin": "\u0d38\u0d48\u0d7b \u0d07\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", + "or": "\u0d05\u0d32\u0d4d\u0d32\u0d46\u0d19\u0d4d\u0d15\u0d3f\u0d7d", + "continue": "\u0d24\u0d41\u0d1f\u0d30\u0d41\u0d15", + "forgotPassword": "\u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d2e\u0d31\u0d28\u0d4d\u0d28\u0d4b?", + "passwordMustContain": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d3f\u0d7d \u0d07\u0d28\u0d3f\u0d2a\u0d4d\u0d2a\u0d31\u0d2f\u0d41\u0d28\u0d4d\u0d28\u0d35 \u0d05\u0d1f\u0d19\u0d4d\u0d19\u0d3f\u0d2f\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d23\u0d02:", + "emailRequired": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d3e\u0d2f \u0d12\u0d30\u0d41 \u0d2b\u0d40\u0d7d\u0d21\u0d3e\u0d23\u0d4d", + "passwordRequired": "Password \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d41\u0d33\u0d4d\u0d33 \u0d12\u0d30\u0d41 \u0d2b\u0d40\u0d7d\u0d21\u0d3e\u0d23\u0d4d" + }, + "error": { + "default": "\u0d38\u0d48\u0d28\u0d4d \u0d07\u0d28\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d28\u0d4d \u0d15\u0d34\u0d3f\u0d2f\u0d41\u0d28\u0d4d\u0d28\u0d3f\u0d32\u0d4d\u0d32.", + "signin": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "oauthsignin": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "redirect_uri_mismatch": "\u0d31\u0d40\u0d21\u0d2f\u0d31\u0d15\u0d4d\u0d1f\u0d4d \u0d2f\u0d41\u0d06\u0d7c\u0d10 \u0d13\u0d24\u0d4d\u0d24\u0d4d \u0d06\u0d2a\u0d4d\u0d2a\u0d4d \u0d15\u0d4b\u0d7a\u0d2b\u0d3f\u0d17\u0d31\u0d47\u0d37\u0d28\u0d41\u0d2e\u0d3e\u0d2f\u0d3f \u0d2a\u0d4a\u0d30\u0d41\u0d24\u0d4d\u0d24\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d41\u0d28\u0d4d\u0d28\u0d3f\u0d32\u0d4d\u0d32.", + "oauthcallbackerror": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "oauthcreateaccount": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "emailcreateaccount": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "callback": "\u0d2e\u0d31\u0d4d\u0d31\u0d4a\u0d30\u0d41 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d7b \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "oauthaccountnotlinked": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d10\u0d21\u0d28\u0d4d\u0d31\u0d3f\u0d31\u0d4d\u0d31\u0d3f \u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d28\u0d4d, \u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d7e \u0d06\u0d26\u0d4d\u0d2f\u0d02 \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a \u0d05\u0d24\u0d47 \u0d05\u0d15\u0d4d\u0d15\u0d57\u0d23\u0d4d\u0d1f\u0d4d \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15.", + "emailsignin": "\u0d07-\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d05\u0d2f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d3e\u0d7b \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d3f\u0d32\u0d4d\u0d32.", + "emailverify": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d2a\u0d30\u0d3f\u0d36\u0d4b\u0d27\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15, \u0d12\u0d30\u0d41 \u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d05\u0d2f\u0d1a\u0d4d\u0d1a\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d23\u0d4d\u0d1f\u0d4d.", + "credentialssignin": "\u0d38\u0d48\u0d7b \u0d07\u0d7b \u0d2a\u0d30\u0d3e\u0d1c\u0d2f\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d4d\u0d1f\u0d41. \u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d7e \u0d28\u0d7d\u0d15\u0d3f\u0d2f \u0d35\u0d3f\u0d36\u0d26\u0d3e\u0d02\u0d36\u0d19\u0d4d\u0d19\u0d7e \u0d36\u0d30\u0d3f\u0d2f\u0d3e\u0d23\u0d4b \u0d0e\u0d28\u0d4d\u0d28\u0d4d \u0d2a\u0d30\u0d3f\u0d36\u0d4b\u0d27\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "sessionrequired": "\u0d08 \u0d2a\u0d47\u0d1c\u0d4d \u0d06\u0d15\u0d4d\u0d38\u0d38\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d28\u0d4d \u0d26\u0d2f\u0d35\u0d3e\u0d2f\u0d3f \u0d38\u0d48\u0d28\u0d3f\u0d7b \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15." + } + }, + "authVerifyEmail": { + "almostThere": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d3f\u0d35\u0d3f\u0d1f\u0d46 \u0d0e\u0d24\u0d4d\u0d24\u0d3e\u0d31\u0d3e\u0d2f\u0d3f! \u0d1e\u0d19\u0d4d\u0d19\u0d7e \u0d12\u0d30\u0d41 \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d05\u0d2f\u0d1a\u0d4d\u0d1a\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d23\u0d4d\u0d1f\u0d4d ", + "verifyEmailLink": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d38\u0d48\u0d28\u0d2a\u0d4d\u0d2a\u0d4d \u0d2a\u0d42\u0d7c\u0d24\u0d4d\u0d24\u0d3f\u0d2f\u0d3e\u0d15\u0d4d\u0d15\u0d3e\u0d7b \u0d06 \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d32\u0d3f\u0d32\u0d46 \u0d32\u0d3f\u0d19\u0d4d\u0d15\u0d3f\u0d7d \u0d15\u0d4d\u0d32\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15.", + "didNotReceive": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d15\u0d23\u0d4d\u0d1f\u0d46\u0d24\u0d4d\u0d24\u0d3e\u0d7b \u0d15\u0d34\u0d3f\u0d2f\u0d41\u0d28\u0d4d\u0d28\u0d3f\u0d32\u0d4d\u0d32\u0d47?", + "resendEmail": "Email \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d05\u0d2f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d15", + "goBack": "\u0d24\u0d3f\u0d30\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d2a\u0d4b\u0d15\u0d42", + "emailSent": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d1c\u0d2f\u0d15\u0d30\u0d2e\u0d3e\u0d2f\u0d3f \u0d05\u0d2f\u0d1a\u0d4d\u0d1a\u0d41.", + "verifyEmail": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d32\u0d3e\u0d38\u0d02 \u0d2a\u0d30\u0d3f\u0d36\u0d4b\u0d27\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15" + }, + "providerButton": { + "continue": "{{provider}} \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d24\u0d41\u0d1f\u0d30\u0d41\u0d15", + "signup": "{{provider}} \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d38\u0d48\u0d7b \u0d05\u0d2a\u0d4d\u0d2a\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15" + }, + "authResetPassword": { + "newPasswordRequired": "\u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d41\u0d33\u0d4d\u0d33 \u0d2b\u0d40\u0d7d\u0d21\u0d3e\u0d23\u0d4d", + "passwordsMustMatch": "\u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d41\u0d15\u0d7e \u0d2a\u0d4a\u0d30\u0d41\u0d24\u0d4d\u0d24\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d23\u0d02", + "confirmPasswordRequired": "\u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d47\u0d23\u0d4d\u0d1f\u0d24\u0d4d \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d3e\u0d23\u0d4d", + "newPassword": "\u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d", + "confirmPassword": "\u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "resetPassword": "\u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d2a\u0d41\u0d28\u0d03\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15" + }, + "authForgotPassword": { + "email": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d32\u0d3e\u0d38\u0d02", + "emailRequired": "\u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d3e\u0d2f \u0d12\u0d30\u0d41 \u0d2b\u0d40\u0d7d\u0d21\u0d3e\u0d23\u0d4d", + "emailSent": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d2a\u0d41\u0d28\u0d03\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d28\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d7c\u0d26\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d7e\u0d15\u0d4d\u0d15\u0d3e\u0d2f\u0d3f {{email}} \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d32\u0d3e\u0d38\u0d02 \u0d2a\u0d30\u0d3f\u0d36\u0d4b\u0d27\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.", + "enterEmail": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d07\u0d2e\u0d46\u0d2f\u0d3f\u0d7d \u0d35\u0d3f\u0d32\u0d3e\u0d38\u0d02 \u0d28\u0d7d\u0d15\u0d41\u0d15, \u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d3e\u0d38\u0d4d \u0d35\u0d47\u0d21\u0d4d \u0d2a\u0d41\u0d28\u0d03\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d28\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d7c\u0d26\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d7e \u0d1e\u0d19\u0d4d\u0d19\u0d7e \u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d7e\u0d15\u0d4d\u0d15\u0d4d \u0d05\u0d2f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d02.", + "resendEmail": "Email \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d05\u0d2f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d15", + "continue": "\u0d24\u0d41\u0d1f\u0d30\u0d41\u0d15", + "goBack": "\u0d24\u0d3f\u0d30\u0d3f\u0d1a\u0d4d\u0d1a\u0d4d \u0d2a\u0d4b\u0d15\u0d42" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0d1a\u0d30\u0d3f\u0d24\u0d4d\u0d30\u0d02 \u0d15\u0d3e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "lastInputs": "\u0d05\u0d35\u0d38\u0d3e\u0d28 \u0d07\u0d7b\u0d2a\u0d41\u0d1f\u0d4d\u0d1f\u0d41\u0d15\u0d7e", + "noInputs": "\u0d36\u0d42\u0d28\u0d4d\u0d2f\u0d2e\u0d3e\u0d2f...", + "loading": "\u0d32\u0d4b\u0d21\u0d3f\u0d02\u0d17\u0d4d..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d02 \u0d07\u0d35\u0d3f\u0d1f\u0d46 \u0d1f\u0d48\u0d2a\u0d4d\u0d2a\u0d41\u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15..." + }, + "speechButton": { + "start": "\u0d31\u0d46\u0d15\u0d4d\u0d15\u0d4b\u0d7c\u0d21\u0d3f\u0d02\u0d17\u0d4d \u0d06\u0d30\u0d02\u0d2d\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "stop": "\u0d31\u0d46\u0d15\u0d4d\u0d15\u0d4b\u0d7c\u0d21\u0d3f\u0d02\u0d17\u0d4d \u0d28\u0d3f\u0d7c\u0d24\u0d4d\u0d24\u0d41\u0d15" + }, + "SubmitButton": { + "sendMessage": "\u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d02 \u0d05\u0d2f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d15", + "stopTask": "\u0d1c\u0d4b\u0d32\u0d3f \u0d28\u0d3f\u0d7c\u0d24\u0d4d\u0d24\u0d41\u0d15" + }, + "UploadButton": { + "attachFiles": "\u0d2b\u0d2f\u0d32\u0d41\u0d15\u0d7e \u0d05\u0d31\u0d4d\u0d31\u0d3e\u0d1a\u0d4d\u0d1a\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15" + }, + "waterMark": { + "text": "\u0d28\u0d3f\u0d7c\u0d2e\u0d4d\u0d2e\u0d3f\u0d1a\u0d4d\u0d1a\u0d24\u0d4d" + } + }, + "Messages": { + "index": { + "running": "\u0d13\u0d1f\u0d41\u0d28\u0d4d\u0d28\u0d41", + "executedSuccessfully": "\u0d35\u0d3f\u0d1c\u0d2f\u0d15\u0d30\u0d2e\u0d3e\u0d2f\u0d3f \u0d28\u0d1f\u0d2a\u0d4d\u0d2a\u0d3f\u0d32\u0d3e\u0d15\u0d4d\u0d15\u0d3f", + "failed": "\u0d2a\u0d30\u0d3e\u0d1c\u0d2f\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d4d\u0d1f\u0d41", + "feedbackUpdated": "\u0d2b\u0d40\u0d21\u0d4d\u0d2c\u0d3e\u0d15\u0d4d\u0d15\u0d4d \u0d05\u0d2a\u0d4d \u0d21\u0d47\u0d31\u0d4d\u0d31\u0d41\u0d1a\u0d46\u0d2f\u0d4d \u0d24\u0d41", + "updating": "\u0d05\u0d2a\u0d4d \u0d21\u0d47\u0d31\u0d4d\u0d31\u0d4d" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d2b\u0d2f\u0d32\u0d41\u0d15\u0d7e \u0d07\u0d35\u0d3f\u0d1f\u0d46 \u0d07\u0d1f\u0d41\u0d15" + }, + "index": { + "failedToUpload": "\u0d05\u0d2a\u0d4d \u0d32\u0d4b\u0d21\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d7d \u0d2a\u0d30\u0d3e\u0d1c\u0d2f\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d4d\u0d1f\u0d41", + "cancelledUploadOf": "\u0d05\u0d2a\u0d4d \u0d32\u0d4b\u0d21\u0d4d \u0d31\u0d26\u0d4d\u0d26\u0d3e\u0d15\u0d4d\u0d15\u0d3f", + "couldNotReachServer": "\u0d38\u0d46\u0d7c\u0d35\u0d31\u0d3f\u0d7d \u0d0e\u0d24\u0d4d\u0d24\u0d3e\u0d7b \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d3f\u0d32\u0d4d\u0d32", + "continuingChat": "\u0d2e\u0d41\u0d2e\u0d4d\u0d2a\u0d24\u0d4d\u0d24\u0d46 \u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d24\u0d41\u0d1f\u0d30\u0d41\u0d28\u0d4d\u0d28\u0d41" + }, + "settings": { + "settingsPanel": "\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d23 \u0d2a\u0d3e\u0d28\u0d7d", + "reset": "\u0d2a\u0d41\u0d28\u0d03\u0d15\u0d4d\u0d30\u0d2e\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "cancel": "\u0d15\u0d4d\u0d2f\u0d3e\u0d7b\u0d38\u0d7d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d4d", + "confirm": "\u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "Feedback: \u0d0e\u0d32\u0d4d\u0d32\u0d3e\u0d02", + "feedbackPositive": "Feedback: \u0d2a\u0d4b\u0d38\u0d3f\u0d31\u0d4d\u0d31\u0d40\u0d35\u0d4d", + "feedbackNegative": "Feedback: \u0d28\u0d46\u0d17\u0d31\u0d4d\u0d31\u0d40\u0d35\u0d4d" + }, + "SearchBar": { + "search": "\u0d24\u0d3f\u0d30\u0d2f\u0d41\u0d15" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0d07\u0d24\u0d4d \u0d24\u0d4d\u0d30\u0d46\u0d21\u0d41\u0d02 \u0d05\u0d24\u0d3f\u0d28\u0d4d\u0d31\u0d46 \u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d02 \u0d18\u0d1f\u0d15\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d02 \u0d07\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d41\u0d02.", + "cancel": "\u0d15\u0d4d\u0d2f\u0d3e\u0d7b\u0d38\u0d7d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d4d", + "confirm": "\u0d38\u0d4d\u0d25\u0d3f\u0d30\u0d40\u0d15\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", + "deletingChat": "\u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d07\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7d", + "chatDeleted": "\u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d28\u0d40\u0d15\u0d4d\u0d15\u0d02 \u0d1a\u0d46\u0d2f\u0d4d\u0d24\u0d41" + }, + "index": { + "pastChats": "Past Chats" + }, + "ThreadList": { + "empty": "\u0d36\u0d42\u0d28\u0d4d\u0d2f\u0d02...", + "today": "\u0d07\u0d28\u0d4d\u0d28\u0d4d", + "yesterday": "\u0d07\u0d28\u0d4d\u0d28\u0d32\u0d46", + "previous7days": "Previous 7 \u0d26\u0d3f\u0d35\u0d38\u0d02", + "previous30days": "Previous 30 \u0d26\u0d3f\u0d35\u0d38\u0d02" + }, + "TriggerButton": { + "closeSidebar": "\u0d38\u0d48\u0d21\u0d4d \u0d2c\u0d3e\u0d7c \u0d05\u0d1f\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d41\u0d15", + "openSidebar": "\u0d38\u0d48\u0d21\u0d4d \u0d2c\u0d3e\u0d7c \u0d24\u0d41\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d15" + } + }, + "Thread": { + "backToChat": "\u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d3f\u0d32\u0d47\u0d15\u0d4d\u0d15\u0d4d \u0d2e\u0d1f\u0d19\u0d4d\u0d19\u0d41\u0d15", + "chatCreatedOn": "\u0d08 \u0d1a\u0d3e\u0d31\u0d4d\u0d31\u0d4d \u0d07\u0d35\u0d3f\u0d1f\u0d46 \u0d38\u0d43\u0d37\u0d4d\u0d1f\u0d3f\u0d1a\u0d4d\u0d1a\u0d41" + } + }, + "header": { + "chat": "\u0d38\u0d02\u0d2d\u0d3e\u0d37\u0d23\u0d02", + "readme": "Readme" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u0d26\u0d3e\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d33\u0d46 \u0d15\u0d4a\u0d23\u0d4d\u0d1f\u0d41\u0d35\u0d30\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d7d \u0d2a\u0d30\u0d3e\u0d1c\u0d2f\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d4d\u0d1f\u0d41:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0d35\u0d3f\u0d1c\u0d2f\u0d15\u0d30\u0d2e\u0d3e\u0d2f\u0d3f \u0d38\u0d02\u0d30\u0d15\u0d4d\u0d37\u0d3f\u0d1a\u0d4d\u0d1a\u0d41", + "requiredApiKeys": "\u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d41\u0d33\u0d4d\u0d33 API \u0d15\u0d40\u0d15\u0d7e", + "requiredApiKeysInfo": "\u0d08 \u0d05\u0d2a\u0d4d\u0d32\u0d3f\u0d15\u0d4d\u0d15\u0d47\u0d37\u0d7b \u0d09\u0d2a\u0d2f\u0d4b\u0d17\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d3f\u0d28\u0d4d, \u0d07\u0d28\u0d3f\u0d2a\u0d4d\u0d2a\u0d31\u0d2f\u0d41\u0d28\u0d4d\u0d28 \u0d0e\u0d2a\u0d3f\u0d10 \u0d15\u0d40\u0d15\u0d7e \u0d06\u0d35\u0d36\u0d4d\u0d2f\u0d2e\u0d3e\u0d23\u0d4d. \u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d33\u0d41\u0d1f\u0d46 \u0d09\u0d2a\u0d15\u0d30\u0d23\u0d24\u0d4d\u0d24\u0d3f\u0d28\u0d4d\u0d31\u0d46 \u0d2a\u0d4d\u0d30\u0d3e\u0d26\u0d47\u0d36\u0d3f\u0d15 \u0d38\u0d02\u0d2d\u0d30\u0d23\u0d24\u0d4d\u0d24\u0d3f\u0d32\u0d3e\u0d23\u0d4d \u0d15\u0d40\u0d15\u0d7e \u0d38\u0d02\u0d2d\u0d30\u0d3f\u0d1a\u0d4d\u0d1a\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d4d." + }, + "Page": { + "notPartOfProject": "\u0d28\u0d3f\u0d19\u0d4d\u0d19\u0d7e \u0d08 \u0d2a\u0d26\u0d4d\u0d27\u0d24\u0d3f\u0d2f\u0d41\u0d1f\u0d46 \u0d2d\u0d3e\u0d17\u0d2e\u0d32\u0d4d\u0d32." + }, + "ResumeButton": { + "resumeChat": "\u0d38\u0d02\u0d2d\u0d3e\u0d37\u0d23\u0d02 \u0d2a\u0d41\u0d28\u0d30\u0d3e\u0d30\u0d02\u0d2d\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/mr.json b/examples/chatbot/.chainlit/translations/mr.json new file mode 100644 index 000000000..f0011ce2d --- /dev/null +++ b/examples/chatbot/.chainlit/translations/mr.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938", + "settingsKey": "S", + "APIKeys": "\u090f\u092a\u0940\u0906\u092f \u0915\u0940\u091c", + "logout": "Logout" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0928\u0935\u0940\u0928 \u0917\u092a\u094d\u092a\u093e" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0915\u093e\u0930\u094d\u092f \u0938\u0942\u091a\u0940", + "loading": "\u0932\u094b\u0921\u093f\u0902\u0917...", + "error": "\u090f\u0915 \u0924\u094d\u0930\u0941\u091f\u0940 \u091d\u093e\u0932\u0940" + } + }, + "attachments": { + "cancelUpload": "\u0905\u092a\u0932\u094b\u0921 \u0930\u0926\u094d\u0926 \u0915\u0930\u093e", + "removeAttachment": "\u0938\u0902\u0932\u0917\u094d\u0928\u0924\u093e \u0915\u093e\u0922\u0942\u0928 \u091f\u093e\u0915\u093e" + }, + "newChatDialog": { + "createNewChat": "\u0928\u0935\u0940\u0928 \u091a\u0945\u091f \u0924\u092f\u093e\u0930 \u0915\u0930\u093e?", + "clearChat": "\u092f\u093e\u092e\u0941\u0933\u0947 \u0938\u0927\u094d\u092f\u093e\u091a\u0947 \u092e\u0947\u0938\u0947\u091c \u0915\u094d\u0932\u093f\u0905\u0930 \u0939\u094b\u0924\u0940\u0932 \u0906\u0923\u093f \u0928\u0935\u0940\u0928 \u091a\u0945\u091f \u0938\u0941\u0930\u0942 \u0939\u094b\u0908\u0932.", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u093e", + "confirm": "\u092a\u0941\u0937\u094d\u091f\u0940 \u0915\u0930\u093e" + }, + "settingsModal": { + "settings": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938", + "expandMessages": "\u0938\u0902\u0926\u0947\u0936 \u093e\u0902\u091a\u093e \u0935\u093f\u0938\u094d\u0924\u093e\u0930 \u0915\u0930\u093e", + "hideChainOfThought": "\u0935\u093f\u091a\u093e\u0930\u093e\u0902\u091a\u0940 \u0938\u093e\u0916\u0933\u0940 \u0932\u092a\u0935\u093e", + "darkMode": "\u0921\u093e\u0930\u094d\u0915 \u092e\u094b\u0921" + }, + "detailsButton": { + "using": "\u0935\u093e\u092a\u0930\u0924", + "running": "\u0927\u093e\u0935\u0924 \u0906\u0939\u0947.", + "took_one": "{{count}} \u092a\u093e\u090a\u0932 \u0909\u091a\u0932\u0932\u0947", + "took_other": "{{count}} \u092a\u093e\u0935\u0932\u0947 \u0909\u091a\u0932\u0932\u0940" + }, + "auth": { + "authLogin": { + "title": "\u0905 \u0945\u092a\u092e\u0927\u094d\u092f\u0947 \u092a\u094d\u0930\u0935\u0947\u0936 \u0915\u0930\u0923\u094d\u092f\u093e\u0938\u093e\u0920\u0940 \u0932\u0949\u0917\u093f\u0928 \u0915\u0930\u093e.", + "form": { + "email": "\u0908\u092e\u0947\u0932 \u092a\u0924\u094d\u0924\u093e", + "password": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921", + "noAccount": "\u0916\u093e\u0924\u0947 \u0928\u093e\u0939\u0940 \u0915\u093e?", + "alreadyHaveAccount": "\u0906\u0927\u0940\u091a \u0916\u093e\u0924\u0947 \u0906\u0939\u0947 \u0915\u093e?", + "signup": "\u0938\u093e\u0907\u0928 \u0905\u092a \u0915\u0930\u093e", + "signin": "\u0938\u093e\u0907\u0928 \u0907\u0928", + "or": "\u0915\u093f\u0902\u0935\u093e", + "continue": "\u091a\u093e\u0932\u0942 \u0920\u0947\u0935\u093e", + "forgotPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0935\u093f\u0938\u0930\u0932\u093e?", + "passwordMustContain": "\u0906\u092a\u0932\u094d\u092f\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921\u092e\u0927\u094d\u092f\u0947 \u0939\u0947 \u0905\u0938\u0923\u0947 \u0906\u0935\u0936\u094d\u092f\u0915 \u0906\u0939\u0947:", + "emailRequired": "\u0908\u092e\u0947\u0932 \u0939\u0947 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930 \u0906\u0939\u0947", + "passwordRequired": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0939\u0947 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930 \u0906\u0939\u0947" + }, + "error": { + "default": "\u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u0938 \u0905\u0915\u094d\u0937\u092e.", + "signin": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "oauthsignin": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "redirect_uri_mismatch": "\u0930\u093f\u0921\u093e\u092f\u0930\u0947\u0915\u094d\u091f \u092f\u0942\u0906\u0930\u0906\u092f \u0911\u0925 \u0905\u0945\u092a \u0915\u0949\u0928\u094d\u092b\u093f\u0917\u0930\u0947\u0936\u0928\u0936\u0940 \u091c\u0941\u0933\u0924 \u0928\u093e\u0939\u0940.", + "oauthcallbackerror": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "oauthcreateaccount": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "emailcreateaccount": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "callback": "\u0935\u0947\u0917\u0933\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u0928 \u0915\u0930\u093e.", + "oauthaccountnotlinked": "\u0906\u092a\u0932\u0940 \u0913\u0933\u0916 \u0928\u093f\u0936\u094d\u091a\u093f\u0924 \u0915\u0930\u0923\u094d\u092f\u093e\u0938\u093e\u0920\u0940, \u0906\u092a\u0923 \u092e\u0942\u0933\u0935\u093e\u092a\u0930\u0932\u0947\u0932\u094d\u092f\u093e \u0916\u093e\u0924\u094d\u092f\u093e\u0938\u0939 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u093e.", + "emailsignin": "\u0908-\u092e\u0947\u0932 \u092a\u093e\u0920\u0935\u0924\u093e \u0906\u0932\u093e \u0928\u093e\u0939\u0940.", + "emailverify": "\u0915\u0943\u092a\u092f\u093e \u0906\u092a\u0932\u094d\u092f\u093e \u0908\u092e\u0947\u0932\u091a\u0940 \u092a\u0921\u0924\u093e\u0933\u0923\u0940 \u0915\u0930\u093e, \u090f\u0915 \u0928\u0935\u0940\u0928 \u0908\u092e\u0947\u0932 \u092a\u093e\u0920\u0935\u093f\u0932\u093e \u0917\u0947\u0932\u093e \u0906\u0939\u0947.", + "credentialssignin": "\u0938\u093e\u0907\u0928 \u0907\u0928 \u0905\u092f\u0936\u0938\u094d\u0935\u0940 \u091d\u093e\u0932\u0947. \u0906\u092a\u0923 \u0926\u093f\u0932\u0947\u0932\u093e \u0924\u092a\u0936\u0940\u0932 \u092f\u094b\u0917\u094d\u092f \u0906\u0939\u0947 \u0939\u0947 \u0924\u092a\u093e\u0938\u093e.", + "sessionrequired": "\u0915\u0943\u092a\u092f\u093e \u092f\u093e \u092a\u0943\u0937\u094d\u0920\u093e\u0935\u0930 \u092a\u094d\u0930\u0935\u0947\u0936 \u0915\u0930\u0923\u094d\u092f\u093e\u0938\u093e\u0920\u0940 \u0938\u093e\u0907\u0928 \u0907\u0928 \u0915\u0930\u093e." + } + }, + "authVerifyEmail": { + "almostThere": "\u0924\u0942 \u091c\u0935\u0933\u091c\u0935\u0933 \u0924\u093f\u0925\u0947\u091a \u0906\u0939\u0947\u0938! \u0906\u092e\u094d\u0939\u0940 \u090f\u0915 \u0908\u092e\u0947\u0932 \u092a\u093e\u0920\u0935\u0932\u093e \u0906\u0939\u0947. ", + "verifyEmailLink": "\u0906\u092a\u0932\u0947 \u0938\u093e\u0907\u0928\u0905\u092a \u092a\u0942\u0930\u094d\u0923 \u0915\u0930\u0923\u094d\u092f\u093e\u0938\u093e\u0920\u0940 \u0915\u0943\u092a\u092f\u093e \u0924\u094d\u092f\u093e \u0908\u092e\u0947\u0932\u092e\u0927\u0940\u0932 \u0932\u093f\u0902\u0915\u0935\u0930 \u0915\u094d\u0932\u093f\u0915 \u0915\u0930\u093e.", + "didNotReceive": "\u0908\u092e\u0947\u0932 \u0938\u093e\u092a\u0921\u0924 \u0928\u093e\u0939\u0940 \u0915\u093e?", + "resendEmail": "\u0908\u092e\u0947\u0932 \u092a\u0941\u0928\u094d\u0939\u093e \u092a\u093e\u0920\u0935\u093e", + "goBack": "\u092a\u0930\u0924 \u091c\u093e", + "emailSent": "\u0908\u092e\u0947\u0932 \u092f\u0936\u0938\u094d\u0935\u0940\u0930\u093f\u0924\u094d\u092f\u093e \u092a\u093e\u0920\u0935\u093f\u0932\u093e.", + "verifyEmail": "\u0906\u092a\u0932\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u094d\u0924\u093e \u092a\u0921\u0924\u093e\u0933\u0942\u0928 \u092a\u0939\u093e" + }, + "providerButton": { + "continue": "{{provider}} \u091a\u093e\u0932\u0942 \u0920\u0947\u0935\u093e", + "signup": "{{provider}} \u0938\u0939 \u0938\u093e\u0907\u0928 \u0905\u092a \u0915\u0930\u093e" + }, + "authResetPassword": { + "newPasswordRequired": "\u0928\u0935\u0940\u0928 \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0939\u0947 \u0906\u0935\u0936\u094d\u092f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930 \u0906\u0939\u0947", + "passwordsMustMatch": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u091c\u0941\u0933\u0923\u0947 \u0906\u0935\u0936\u094d\u092f\u0915 \u0906\u0939\u0947", + "confirmPasswordRequired": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0906\u0935\u0936\u094d\u092f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930 \u0906\u0939\u0947 \u092f\u093e\u091a\u0940 \u092a\u0941\u0937\u094d\u091f\u0940 \u0915\u0930\u093e", + "newPassword": "\u0928\u0935\u0940\u0928 \u092a\u093e\u0938\u0935\u0930\u094d\u0921", + "confirmPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u091a\u0940 \u092a\u0941\u0937\u094d\u091f\u0940 \u0915\u0930\u093e", + "resetPassword": "\u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u093e" + }, + "authForgotPassword": { + "email": "\u0908\u092e\u0947\u0932 \u092a\u0924\u094d\u0924\u093e", + "emailRequired": "\u0908\u092e\u0947\u0932 \u0939\u0947 \u090f\u0915 \u0906\u0935\u0936\u094d\u092f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930 \u0906\u0939\u0947", + "emailSent": "\u0906\u092a\u0932\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u094d\u092f\u093e \u0938\u0942\u091a\u0928\u093e\u0902\u0938\u093e\u0920\u0940 \u0915\u0943\u092a\u092f\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u094d\u0924\u093e {{email}} \u0924\u092a\u093e\u0938\u093e.", + "enterEmail": "\u0906\u092a\u0932\u093e \u0908\u092e\u0947\u0932 \u092a\u0924\u094d\u0924\u093e \u092a\u094d\u0930\u0935\u093f\u0937\u094d\u091f \u0915\u0930\u093e \u0906\u0923\u093f \u0906\u092e\u094d\u0939\u0940 \u0906\u092a\u0932\u094d\u092f\u093e\u0932\u093e \u0906\u092a\u0932\u093e \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u0930\u0940\u0938\u0947\u091f \u0915\u0930\u0923\u094d\u092f\u093e\u091a\u094d\u092f\u093e \u0938\u0942\u091a\u0928\u093e \u092a\u093e\u0920\u0935\u0942.", + "resendEmail": "\u0908\u092e\u0947\u0932 \u092a\u0941\u0928\u094d\u0939\u093e \u092a\u093e\u0920\u0935\u093e", + "continue": "\u091a\u093e\u0932\u0942 \u0920\u0947\u0935\u093e", + "goBack": "\u092a\u0930\u0924 \u091c\u093e" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0907\u0924\u093f\u0939\u093e\u0938 \u0926\u093e\u0916\u0935\u093e", + "lastInputs": "\u0936\u0947\u0935\u091f\u091a\u0940 \u092e\u093e\u0939\u093f\u0924\u0940", + "noInputs": "\u0907\u0924\u0915\u0940 \u0930\u093f\u0915\u093e\u092e\u0940...", + "loading": "\u0932\u094b\u0921\u093f\u0902\u0917..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0924\u0941\u092e\u091a\u093e \u092e\u0947\u0938\u0947\u091c \u0907\u0925\u0947 \u091f\u093e\u0908\u092a \u0915\u0930\u093e..." + }, + "speechButton": { + "start": "\u0930\u0947\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917 \u0938\u0941\u0930\u0942 \u0915\u0930\u093e", + "stop": "\u0930\u0947\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917 \u0925\u093e\u0902\u092c\u0935\u093e" + }, + "SubmitButton": { + "sendMessage": "\u0938\u0902\u0926\u0947\u0936 \u092a\u093e\u0920\u0935\u093e", + "stopTask": "\u0915\u093e\u0930\u094d\u092f \u0925\u093e\u0902\u092c\u0935\u093e" + }, + "UploadButton": { + "attachFiles": "\u092b\u093e\u0908\u0932\u094d\u0938 \u0938\u0902\u0932\u0917\u094d\u0928 \u0915\u0930\u093e" + }, + "waterMark": { + "text": "\u092f\u093e\u0938\u0939 \u092c\u093e\u0902\u0927\u0932\u0947 \u0906\u0939\u0947" + } + }, + "Messages": { + "index": { + "running": "\u0927\u093e\u0935\u0924 \u0906\u0939\u0947.", + "executedSuccessfully": "\u092f\u0936\u0938\u094d\u0935\u0940\u0930\u093f\u0924\u094d\u092f\u093e \u0930\u093e\u092c\u0935\u093f\u0932\u0940", + "failed": "\u0905\u092a\u092f\u0936\u0940 \u0920\u0930\u0932\u0947", + "feedbackUpdated": "\u0905\u092d\u093f\u092a\u094d\u0930\u093e\u092f \u0905\u0926\u094d\u092f\u092f\u093e\u0935\u0924", + "updating": "\u0905\u0926\u094d\u092f\u092f\u093e\u0935\u0924 \u0915\u0930\u0923\u0947" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0906\u092a\u0932\u094d\u092f\u093e \u092b\u093e\u092f\u0932\u0940 \u092f\u0947\u0925\u0947 \u091f\u093e\u0915\u093e" + }, + "index": { + "failedToUpload": "\u0905\u092a\u0932\u094b\u0921 \u0915\u0930\u0923\u094d\u092f\u093e\u0924 \u0905\u092a\u092f\u0936 \u0906\u0932\u0947", + "cancelledUploadOf": "\u0930\u0926\u094d\u0926 \u0915\u0947\u0932\u0947\u0932\u0947 \u0905\u092a\u0932\u094b\u0921", + "couldNotReachServer": "\u0938\u0930\u094d\u0935\u094d\u0939\u0930\u092a\u0930\u094d\u092f\u0902\u0924 \u092a\u094b\u0939\u094b\u091a\u0942 \u0936\u0915\u0932\u0947 \u0928\u093e\u0939\u0940", + "continuingChat": "\u092e\u093e\u0917\u0940\u0932 \u0917\u092a\u094d\u092a\u093e \u091a\u093e\u0932\u0942 \u0920\u0947\u0935\u093e" + }, + "settings": { + "settingsPanel": "\u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938 \u092a\u0945\u0928\u0947\u0932", + "reset": "\u0930\u0940\u0938\u0947\u091f \u0915\u0930\u093e", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u093e", + "confirm": "\u092a\u0941\u0937\u094d\u091f\u0940 \u0915\u0930\u093e" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u0905\u092d\u093f\u092a\u094d\u0930\u093e\u092f: \u0938\u0930\u094d\u0935", + "feedbackPositive": "\u0905\u092d\u093f\u092a\u094d\u0930\u093e\u092f: \u0938\u0915\u093e\u0930\u093e\u0924\u094d\u092e\u0915", + "feedbackNegative": "\u0905\u092d\u093f\u092a\u094d\u0930\u093e\u092f: \u0928\u0915\u093e\u0930\u093e\u0924\u094d\u092e\u0915" + }, + "SearchBar": { + "search": "\u0936\u094b\u0927\u0923\u0947" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0939\u0947 \u0927\u093e\u0917\u093e \u0924\u0938\u0947\u091a \u0924\u094d\u092f\u093e\u0924\u0940\u0932 \u0938\u0902\u0926\u0947\u0936 \u0906\u0923\u093f \u0918\u091f\u0915 \u0921\u093f\u0932\u0940\u091f \u0915\u0930\u0947\u0932.", + "cancel": "\u0930\u0926\u094d\u0926 \u0915\u0930\u093e", + "confirm": "\u092a\u0941\u0937\u094d\u091f\u0940 \u0915\u0930\u093e", + "deletingChat": "\u091a\u0945\u091f \u0921\u093f\u0932\u0940\u091f \u0915\u0930\u0923\u0947", + "chatDeleted": "\u091a\u0945\u091f \u0921\u093f\u0932\u0940\u091f" + }, + "index": { + "pastChats": "\u092e\u093e\u0917\u0940\u0932 \u0917\u092a\u094d\u092a\u093e" + }, + "ThreadList": { + "empty": "\u0930\u093f\u0915\u094d\u0924\u0964\u0964\u0964", + "today": "\u0906\u091c", + "yesterday": "\u0915\u093e\u0932", + "previous7days": "\u092e\u093e\u0917\u0940\u0932 7 \u0926\u093f\u0935\u0938", + "previous30days": "\u092e\u093e\u0917\u0940\u0932 \u0969\u0966 \u0926\u093f\u0935\u0938" + }, + "TriggerButton": { + "closeSidebar": "\u0938\u093e\u0907\u0921\u092c\u093e\u0930 \u092c\u0902\u0926 \u0915\u0930\u093e", + "openSidebar": "\u0913\u092a\u0928 \u0938\u093e\u0907\u0921\u092c\u093e\u0930" + } + }, + "Thread": { + "backToChat": "\u092a\u0930\u0924 \u0917\u092a\u094d\u092a\u093e \u092e\u093e\u0930\u093e\u092f\u0932\u093e \u091c\u093e", + "chatCreatedOn": "\u0939\u0947 \u091a\u0945\u091f \u0924\u092f\u093e\u0930 \u0915\u0930\u0923\u094d\u092f\u093e\u0924 \u0906\u0932\u0947 \u0939\u094b\u0924\u0947." + } + }, + "header": { + "chat": "\u092c\u0915\u0935\u093e\u0926 \u0915\u0930\u0923\u0947\u0902", + "readme": "\u0935\u093e\u091a\u093e" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u092a\u094d\u0930\u0926\u093e\u0924\u094d\u092f\u093e\u0902\u0928\u093e \u0906\u0923\u0923\u094d\u092f\u093e\u0924 \u0905\u092a\u092f\u0936\u0940:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u092f\u0936\u0938\u094d\u0935\u0940\u0930\u093f\u0924\u094d\u092f\u093e \u0935\u093e\u091a\u0935\u0932\u0947", + "requiredApiKeys": "\u0906\u0935\u0936\u094d\u092f\u0915 \u090f\u092a\u0940\u0906\u092f \u091a\u093e\u0935\u094d\u092f\u093e", + "requiredApiKeysInfo": "\u0939\u0947 \u0905\u0945\u092a \u0935\u093e\u092a\u0930\u0923\u094d\u092f\u093e\u0938\u093e\u0920\u0940 \u0916\u093e\u0932\u0940\u0932 \u090f\u092a\u0940\u0906\u092f \u091a\u093e\u0935\u094d\u092f\u093e \u0906\u0935\u0936\u094d\u092f\u0915 \u0906\u0939\u0947\u0924. \u091a\u093e\u0935\u094d\u092f\u093e \u0906\u092a\u0932\u094d\u092f\u093e \u0921\u093f\u0935\u094d\u0939\u093e\u0907\u0938\u091a\u094d\u092f\u093e \u0938\u094d\u0925\u093e\u0928\u093f\u0915 \u0938\u094d\u091f\u094b\u0930\u0947\u091c\u0935\u0930 \u0938\u0902\u0917\u094d\u0930\u0939\u093f\u0924 \u0915\u0947\u0932\u094d\u092f\u093e \u091c\u093e\u0924\u093e\u0924." + }, + "Page": { + "notPartOfProject": "\u0924\u0941\u092e\u094d\u0939\u0940 \u092f\u093e \u092a\u094d\u0930\u0915\u0932\u094d\u092a\u093e\u091a\u093e \u092d\u093e\u0917 \u0928\u093e\u0939\u0940." + }, + "ResumeButton": { + "resumeChat": "\u091a\u0945\u091f \u092a\u0941\u0928\u094d\u0939\u093e \u0938\u0941\u0930\u0942 \u0915\u0930\u093e" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/ta.json b/examples/chatbot/.chainlit/translations/ta.json new file mode 100644 index 000000000..b1eccf580 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/ta.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0b85\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", + "settingsKey": "S", + "APIKeys": "API \u0bb5\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", + "logout": "\u0bb5\u0bc6\u0bb3\u0bbf\u0baf\u0bc7\u0bb1\u0bc1" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0baa\u0ba3\u0bbf \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", + "loading": "\u0b8f\u0bb1\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1...", + "error": "\u0b92\u0bb0\u0bc1 \u0baa\u0bbf\u0bb4\u0bc8 \u0b8f\u0bb1\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1" + } + }, + "attachments": { + "cancelUpload": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc7\u0bb1\u0bcd\u0bb1\u0ba4\u0bcd\u0ba4\u0bc8 \u0bb0\u0ba4\u0bcd\u0ba4\u0bc1\u0b9a\u0bc6\u0baf\u0bcd", + "removeAttachment": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc8 \u0b85\u0b95\u0bb1\u0bcd\u0bb1\u0bc1" + }, + "newChatDialog": { + "createNewChat": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bc8 \u0b89\u0bb0\u0bc1\u0bb5\u0bbe\u0b95\u0bcd\u0b95\u0bb5\u0bbe?", + "clearChat": "\u0b87\u0ba4\u0bc1 \u0ba4\u0bb1\u0bcd\u0baa\u0bcb\u0ba4\u0bc8\u0baf \u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bbf\u0b95\u0bb3\u0bc8 \u0b85\u0bb4\u0bbf\u0ba4\u0bcd\u0ba4\u0bc1 \u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bc8\u0ba4\u0bcd \u0ba4\u0bca\u0b9f\u0b99\u0bcd\u0b95\u0bc1\u0bae\u0bcd.", + "cancel": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1", + "confirm": "\u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0b9a\u0bc6\u0baf\u0bcd" + }, + "settingsModal": { + "settings": "\u0b85\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", + "expandMessages": "\u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bbf\u0b95\u0bb3\u0bc8 \u0bb5\u0bbf\u0bb0\u0bbf\u0bb5\u0bbe\u0b95\u0bcd\u0b95\u0bc1", + "hideChainOfThought": "\u0b9a\u0bbf\u0ba8\u0bcd\u0ba4\u0ba9\u0bc8\u0b9a\u0bcd \u0b9a\u0b99\u0bcd\u0b95\u0bbf\u0bb2\u0bbf\u0baf\u0bc8 \u0bae\u0bb1\u0bc8\u0ba4\u0bcd\u0ba4\u0bc1", + "darkMode": "\u0b87\u0bb0\u0bc1\u0ba3\u0bcd\u0b9f \u0baa\u0baf\u0ba9\u0bcd\u0bae\u0bc1\u0bb1\u0bc8" + }, + "detailsButton": { + "using": "\u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf", + "running": "\u0b93\u0b9f\u0bc1\u0ba4\u0bb2\u0bcd", + "took_one": "{{count}} \u0b85\u0b9f\u0bbf \u0b8e\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1 \u0bb5\u0bc8\u0ba4\u0bcd\u0ba4\u0bbe\u0bb0\u0bcd", + "took_other": "{{count}} \u0baa\u0b9f\u0bbf\u0b95\u0bb3\u0bc8 \u0b8e\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbe\u0bb0\u0bcd" + }, + "auth": { + "authLogin": { + "title": "\u0baa\u0baf\u0ba9\u0bcd\u0baa\u0bbe\u0b9f\u0bcd\u0b9f\u0bc8 \u0b85\u0ba3\u0bc1\u0b95 \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0b95.", + "form": { + "email": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", + "password": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd", + "noAccount": "\u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1 \u0b87\u0bb2\u0bcd\u0bb2\u0bc8\u0baf\u0bbe?", + "alreadyHaveAccount": "\u0b8f\u0bb1\u0bcd\u0b95\u0ba9\u0bb5\u0bc7 \u0b92\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1 \u0b89\u0bb3\u0bcd\u0bb3\u0ba4\u0bbe?", + "signup": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc1\u0baa\u0bc6\u0bb1\u0bc1", + "signin": "\u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0b95", + "or": "\u0b85\u0bb2\u0bcd\u0bb2\u0ba4\u0bc1", + "continue": "\u0ba4\u0bca\u0b9f\u0bb0\u0bcd", + "forgotPassword": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0bae\u0bb1\u0ba8\u0bcd\u0ba4\u0bc1\u0bb5\u0bbf\u0b9f\u0bcd\u0b9f\u0bc0\u0bb0\u0bcd\u0b95\u0bb3\u0bbe?", + "passwordMustContain": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bbf\u0bb2\u0bcd \u0b87\u0bb5\u0bc8 \u0b87\u0bb0\u0bc1\u0b95\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd:", + "emailRequired": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0b92\u0bb0\u0bc1 \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 \u0baa\u0bc1\u0bb2\u0bae\u0bcd", + "passwordRequired": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 \u0baa\u0bc1\u0bb2\u0bae\u0bcd" + }, + "error": { + "default": "\u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0b87\u0baf\u0bb2\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8.", + "signin": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "oauthsignin": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "redirect_uri_mismatch": "\u0bb5\u0bb4\u0bbf\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1 URI oauth \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1 \u0b89\u0bb3\u0bcd\u0bb3\u0bae\u0bc8\u0bb5\u0bc1\u0b9f\u0ba9\u0bcd \u0baa\u0bca\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8.", + "oauthcallbackerror": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "oauthcreateaccount": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "emailcreateaccount": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "callback": "\u0bb5\u0bc7\u0bb1\u0bca\u0bb0\u0bc1 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf \u0bae\u0bc1\u0baf\u0bb1\u0bcd\u0b9a\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "oauthaccountnotlinked": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b85\u0b9f\u0bc8\u0baf\u0bbe\u0bb3\u0ba4\u0bcd\u0ba4\u0bc8 \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4, \u0ba8\u0bc0\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0bae\u0bc1\u0ba4\u0bb2\u0bbf\u0bb2\u0bcd \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf \u0b85\u0ba4\u0bc7 \u0b95\u0ba3\u0b95\u0bcd\u0b95\u0bc1\u0b9f\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf\u0bb5\u0bc1\u0bae\u0bcd.", + "emailsignin": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bc8 \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa \u0b87\u0baf\u0bb2\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8.", + "emailverify": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bc8 \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b99\u0bcd\u0b95\u0bb3\u0bcd, \u0b92\u0bb0\u0bc1 \u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1.", + "credentialssignin": "\u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0bb5\u0bc1 \u0ba4\u0bcb\u0bb2\u0bcd\u0bb5\u0bbf\u0baf\u0bc1\u0bb1\u0bcd\u0bb1\u0ba4\u0bc1. \u0ba8\u0bc0\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0bb5\u0bb4\u0b99\u0bcd\u0b95\u0bbf\u0baf \u0bb5\u0bbf\u0bb5\u0bb0\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bb0\u0bbf\u0baf\u0bbe\u0ba9\u0ba4\u0bbe \u0b8e\u0ba9\u0bcd\u0bb1\u0bc1 \u0b9a\u0bb0\u0bbf\u0baa\u0bbe\u0bb0\u0bcd\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "sessionrequired": "\u0b87\u0ba8\u0bcd\u0ba4\u0baa\u0bcd \u0baa\u0b95\u0bcd\u0b95\u0ba4\u0bcd\u0ba4\u0bc8 \u0b85\u0ba3\u0bc1\u0b95 \u0b89\u0bb3\u0bcd\u0ba8\u0bc1\u0bb4\u0bc8\u0baf\u0bb5\u0bc1\u0bae\u0bcd." + } + }, + "authVerifyEmail": { + "almostThere": "\u0ba8\u0bc0\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b95\u0bbf\u0b9f\u0bcd\u0b9f\u0ba4\u0bcd\u0ba4\u0b9f\u0bcd\u0b9f \u0bb5\u0ba8\u0bcd\u0ba4\u0bc1\u0bb5\u0bbf\u0b9f\u0bcd\u0b9f\u0bc0\u0bb0\u0bcd\u0b95\u0bb3\u0bcd! -\u0b95\u0bcd\u0b95\u0bc1 \u0b92\u0bb0\u0bc1 \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0baf\u0bc1\u0bb3\u0bcd\u0bb3\u0bcb\u0bae\u0bcd ", + "verifyEmailLink": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0baa\u0ba4\u0bbf\u0bb5\u0bc1\u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bb2\u0bc8 \u0ba8\u0bbf\u0bb1\u0bc8\u0bb5\u0bc1\u0b9a\u0bc6\u0baf\u0bcd\u0baf \u0b85\u0ba8\u0bcd\u0ba4 \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bbf\u0bb2\u0bcd \u0b89\u0bb3\u0bcd\u0bb3 \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc8\u0b95\u0bcd \u0b95\u0bbf\u0bb3\u0bbf\u0b95\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0baf\u0bb5\u0bc1\u0bae\u0bcd.", + "didNotReceive": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bc8\u0b95\u0bcd \u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95 \u0bae\u0bc1\u0b9f\u0bbf\u0baf\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8\u0baf\u0bbe?", + "resendEmail": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bc8 \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0bb5\u0bc1\u0bae\u0bcd", + "goBack": "\u0baa\u0bbf\u0ba9\u0bcd \u0b9a\u0bc6\u0bb2\u0bcd", + "emailSent": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bb5\u0bc6\u0bb1\u0bcd\u0bb1\u0bbf\u0b95\u0bb0\u0bae\u0bbe\u0b95 \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1.", + "verifyEmail": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b88\u0bae\u0bc6\u0baf\u0bbf\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf\u0baf\u0bc8\u0b9a\u0bcd \u0b9a\u0bb0\u0bbf\u0baa\u0bbe\u0bb0\u0bcd\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd" + }, + "providerButton": { + "continue": "{{provider}} \u0b89\u0b9f\u0ba9\u0bcd \u0ba4\u0bca\u0b9f\u0bb0\u0bb5\u0bc1\u0bae\u0bcd", + "signup": "{{provider}} \u0b89\u0b9f\u0ba9\u0bcd \u0baa\u0ba4\u0bbf\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95" + }, + "authResetPassword": { + "newPasswordRequired": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 \u0baa\u0bc1\u0bb2\u0bae\u0bcd", + "passwordsMustMatch": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd \u0baa\u0bca\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd", + "confirmPasswordRequired": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bb5\u0bc1\u0bae\u0bcd \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 \u0baa\u0bc1\u0bb2\u0bae\u0bcd", + "newPassword": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd", + "confirmPassword": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bb5\u0bc1\u0bae\u0bcd", + "resetPassword": "\u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bae\u0bc8" + }, + "authForgotPassword": { + "email": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", + "emailRequired": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0b92\u0bb0\u0bc1 \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 \u0baa\u0bc1\u0bb2\u0bae\u0bcd", + "emailSent": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0ba4\u0bb1\u0bcd\u0b95\u0bbe\u0ba9 \u0bb5\u0bb4\u0bbf\u0bae\u0bc1\u0bb1\u0bc8\u0b95\u0bb3\u0bc1\u0b95\u0bcd\u0b95\u0bc1 {{email}} \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf\u0baf\u0bc8 \u0b9a\u0bb0\u0bbf\u0baa\u0bbe\u0bb0\u0bcd\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd.", + "enterEmail": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf\u0baf\u0bc8 \u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0b9f\u0bb5\u0bc1\u0bae\u0bcd, \u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b95\u0b9f\u0bb5\u0bc1\u0b9a\u0bcd\u0b9a\u0bca\u0bb2\u0bcd\u0bb2\u0bc8 \u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bae\u0bc8\u0b95\u0bcd\u0b95 \u0ba8\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bc1\u0b95\u0bcd\u0b95\u0bc1 \u0bb5\u0bb4\u0bbf\u0bae\u0bc1\u0bb1\u0bc8\u0b95\u0bb3\u0bc8 \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0bc1\u0bb5\u0bcb\u0bae\u0bcd.", + "resendEmail": "\u0bae\u0bbf\u0ba9\u0bcd\u0ba9\u0b9e\u0bcd\u0b9a\u0bb2\u0bc8 \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0bb5\u0bc1\u0bae\u0bcd", + "continue": "\u0ba4\u0bca\u0b9f\u0bb0\u0bcd", + "goBack": "\u0baa\u0bbf\u0ba9\u0bcd \u0b9a\u0bc6\u0bb2\u0bcd" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0bb5\u0bb0\u0bb2\u0bbe\u0bb1\u0bcd\u0bb1\u0bc8\u0b95\u0bcd \u0b95\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", + "lastInputs": "\u0b95\u0b9f\u0bc8\u0b9a\u0bbf \u0b89\u0bb3\u0bcd\u0bb3\u0bc0\u0b9f\u0bc1\u0b95\u0bb3\u0bcd", + "noInputs": "\u0b85\u0bb5\u0bcd\u0bb5\u0bb3\u0bb5\u0bc1 \u0bb5\u0bc6\u0bb1\u0bc1\u0bae\u0bc8...", + "loading": "\u0b8f\u0bb1\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bbf\u0baf\u0bc8 \u0b87\u0b99\u0bcd\u0b95\u0bc7 \u0ba4\u0b9f\u0bcd\u0b9f\u0b9a\u0bcd\u0b9a\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95..." + }, + "speechButton": { + "start": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0baf\u0ba4\u0bcd \u0ba4\u0bca\u0b9f\u0b99\u0bcd\u0b95\u0bc1", + "stop": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0bb5\u0ba4\u0bc8 \u0ba8\u0bbf\u0bb1\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1" + }, + "SubmitButton": { + "sendMessage": "\u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bbf \u0b85\u0ba9\u0bc1\u0baa\u0bcd\u0baa\u0bc1", + "stopTask": "\u0baa\u0ba3\u0bbf\u0baf\u0bc8 \u0ba8\u0bbf\u0bb1\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1" + }, + "UploadButton": { + "attachFiles": "\u0b95\u0bcb\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bc8 \u0b87\u0ba3\u0bc8\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd" + }, + "waterMark": { + "text": "\u0b89\u0b9f\u0ba9\u0bcd \u0b95\u0b9f\u0bcd\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1" + } + }, + "Messages": { + "index": { + "running": "\u0b93\u0b9f\u0bc1\u0ba4\u0bb2\u0bcd", + "executedSuccessfully": "\u0bb5\u0bc6\u0bb1\u0bcd\u0bb1\u0bbf\u0b95\u0bb0\u0bae\u0bbe\u0b95 \u0b9a\u0bc6\u0baf\u0bb2\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", + "failed": "\u0ba4\u0bcb\u0bb2\u0bcd\u0bb5\u0bbf\u0baf\u0bc1\u0bb1\u0bcd\u0bb1\u0ba4\u0bc1", + "feedbackUpdated": "\u0b95\u0bb0\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1 \u0baa\u0bc1\u0ba4\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", + "updating": "\u0baa\u0bc1\u0ba4\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb1\u0ba4\u0bc1" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b95\u0bcb\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bc8 \u0b87\u0b99\u0bcd\u0b95\u0bc7 \u0bb5\u0bbf\u0b9f\u0bc1\u0b99\u0bcd\u0b95\u0bb3\u0bcd:" + }, + "index": { + "failedToUpload": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1\u0bb5\u0ba4\u0bbf\u0bb2\u0bcd \u0ba4\u0bcb\u0bb2\u0bcd\u0bb5\u0bbf", + "cancelledUploadOf": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0baf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0baa\u0ba4\u0bbf\u0bb5\u0bc7\u0bb1\u0bcd\u0bb1\u0bae\u0bcd", + "couldNotReachServer": "\u0b9a\u0bc7\u0bb5\u0bc8\u0baf\u0b95\u0ba4\u0bcd\u0ba4\u0bc8 \u0b85\u0b9f\u0bc8\u0baf \u0bae\u0bc1\u0b9f\u0bbf\u0baf\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8", + "continuingChat": "\u0ba4\u0bca\u0b9f\u0bb0\u0bc1\u0bae\u0bcd \u0bae\u0bc1\u0ba8\u0bcd\u0ba4\u0bc8\u0baf \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8" + }, + "settings": { + "settingsPanel": "\u0b85\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd \u0b95\u0bc1\u0bb4\u0bc1", + "reset": "\u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bae\u0bc8", + "cancel": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1", + "confirm": "\u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0b9a\u0bc6\u0baf\u0bcd" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0bc2\u0b9f\u0bcd\u0b9f\u0bae\u0bcd: \u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc1\u0bae\u0bcd", + "feedbackPositive": "\u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0bc2\u0b9f\u0bcd\u0b9f\u0bae\u0bcd: \u0ba8\u0bc7\u0bb0\u0bcd\u0bae\u0bb1\u0bc8", + "feedbackNegative": "\u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0bc2\u0b9f\u0bcd\u0b9f\u0bae\u0bcd: \u0b8e\u0ba4\u0bbf\u0bb0\u0bcd\u0bae\u0bb1\u0bc8" + }, + "SearchBar": { + "search": "\u0ba4\u0bc7\u0b9f\u0bc1" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0b87\u0ba4\u0bc1 \u0ba8\u0bc2\u0bb2\u0bcd \u0bae\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0b85\u0ba4\u0ba9\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bbf\u0b95\u0bb3\u0bcd \u0bae\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0b95\u0bc2\u0bb1\u0bc1\u0b95\u0bb3\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0bae\u0bcd.", + "cancel": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1", + "confirm": "\u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0b9a\u0bc6\u0baf\u0bcd", + "deletingChat": "\u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1", + "chatDeleted": "\u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1" + }, + "index": { + "pastChats": "\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0b95\u0bb3\u0bcd" + }, + "ThreadList": { + "empty": "\u0b95\u0bbe\u0bb2\u0bbf\u0baf\u0bbe\u0ba9...", + "today": "\u0b87\u0ba9\u0bcd\u0bb1\u0bc1", + "yesterday": "\u0ba8\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1", + "previous7days": "\u0bae\u0bc1\u0ba8\u0bcd\u0ba4\u0bc8\u0baf 7 \u0ba8\u0bbe\u0b9f\u0bcd\u0b95\u0bb3\u0bcd", + "previous30days": "\u0bae\u0bc1\u0ba8\u0bcd\u0ba4\u0bc8\u0baf 30 \u0ba8\u0bbe\u0b9f\u0bcd\u0b95\u0bb3\u0bcd" + }, + "TriggerButton": { + "closeSidebar": "\u0baa\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bc8 \u0bae\u0bc2\u0b9f\u0bc1", + "openSidebar": "\u0baa\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bc8\u0ba4\u0bcd \u0ba4\u0bbf\u0bb1\u0b95\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd" + } + }, + "Thread": { + "backToChat": "\u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0b95\u0bcd\u0b95\u0bc1 \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bcd\u0bb2\u0bb5\u0bc1\u0bae\u0bcd", + "chatCreatedOn": "\u0b87\u0ba8\u0bcd\u0ba4 \u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8 \u0b89\u0bb0\u0bc1\u0bb5\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0ba4\u0bc7\u0ba4\u0bbf" + } + }, + "header": { + "chat": "\u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8", + "readme": "\u0bb0\u0bc0\u0b9f\u0bcd\u0bae\u0bc0" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u0bb5\u0bb4\u0b99\u0bcd\u0b95\u0bc1\u0ba8\u0bb0\u0bcd\u0b95\u0bb3\u0bc8\u0baa\u0bcd \u0baa\u0bc6\u0bb1\u0bc1\u0bb5\u0ba4\u0bbf\u0bb2\u0bcd \u0ba4\u0bcb\u0bb2\u0bcd\u0bb5\u0bbf:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0bb5\u0bc6\u0bb1\u0bcd\u0bb1\u0bbf\u0b95\u0bb0\u0bae\u0bbe\u0b95 \u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", + "requiredApiKeys": "\u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 API \u0bb5\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", + "requiredApiKeysInfo": "\u0b87\u0ba8\u0bcd\u0ba4 \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0bbe\u0b9f\u0bcd\u0b9f\u0bc8\u0baa\u0bcd \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4, \u0baa\u0bbf\u0ba9\u0bcd\u0bb5\u0bb0\u0bc1\u0bae\u0bcd API \u0bb5\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd \u0ba4\u0bc7\u0bb5\u0bc8. \u0bb5\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd \u0b89\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bbe\u0ba4\u0ba9\u0ba4\u0bcd\u0ba4\u0bbf\u0ba9\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0bc2\u0bb0\u0bcd \u0b9a\u0bc7\u0bae\u0bbf\u0baa\u0bcd\u0baa\u0b95\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0bae\u0bcd." + }, + "Page": { + "notPartOfProject": "\u0ba8\u0bc0\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b87\u0ba8\u0bcd\u0ba4\u0ba4\u0bcd \u0ba4\u0bbf\u0b9f\u0bcd\u0b9f\u0ba4\u0bcd\u0ba4\u0bbf\u0ba9\u0bcd \u0b92\u0bb0\u0bc1 \u0baa\u0b95\u0bc1\u0ba4\u0bbf\u0baf\u0bbe\u0b95 \u0b87\u0bb2\u0bcd\u0bb2\u0bc8." + }, + "ResumeButton": { + "resumeChat": "\u0b85\u0bb0\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bc8 \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0ba4\u0bca\u0b9f\u0b99\u0bcd\u0b95\u0bb5\u0bc1\u0bae\u0bcd" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/te.json b/examples/chatbot/.chainlit/translations/te.json new file mode 100644 index 000000000..60ada3052 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/te.json @@ -0,0 +1,231 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u0c38\u0c46\u0c1f\u0c4d\u0c1f\u0c3f\u0c02\u0c17\u0c4d \u0c32\u0c41", + "settingsKey": "S", + "APIKeys": "API Keys", + "logout": "Logout" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c1a\u0c3e\u0c1f\u0c4d" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u0c1f\u0c3e\u0c38\u0c4d\u0c15\u0c4d \u0c32\u0c3f\u0c38\u0c4d\u0c1f\u0c4d", + "loading": "\u0c32\u0c4b\u0c21\u0c3f\u0c02\u0c17\u0c4d...", + "error": "\u0c12\u0c15 \u0c26\u0c4b\u0c37\u0c02 \u0c38\u0c02\u0c2d\u0c35\u0c3f\u0c02\u0c1a\u0c3f\u0c02\u0c26\u0c3f" + } + }, + "attachments": { + "cancelUpload": "\u0c05\u0c2a\u0c4d \u0c32\u0c4b\u0c21\u0c4d \u0c30\u0c26\u0c4d\u0c26\u0c41 \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f", + "removeAttachment": "\u0c05\u0c1f\u0c3e\u0c1a\u0c4d \u0c2e\u0c46\u0c02\u0c1f\u0c4d \u0c24\u0c4a\u0c32\u0c17\u0c3f\u0c02\u0c1a\u0c41" + }, + "newChatDialog": { + "createNewChat": "\u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c1a\u0c3e\u0c1f\u0c4d \u0c38\u0c43\u0c37\u0c4d\u0c1f\u0c3f\u0c02\u0c1a\u0c3e\u0c32\u0c3e?", + "clearChat": "\u0c07\u0c26\u0c3f \u0c2a\u0c4d\u0c30\u0c38\u0c4d\u0c24\u0c41\u0c24 \u0c38\u0c02\u0c26\u0c47\u0c36\u0c3e\u0c32\u0c28\u0c41 \u0c15\u0c4d\u0c32\u0c3f\u0c2f\u0c30\u0c4d \u0c1a\u0c47\u0c38\u0c4d\u0c24\u0c41\u0c02\u0c26\u0c3f \u0c2e\u0c30\u0c3f\u0c2f\u0c41 \u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c1a\u0c3e\u0c1f\u0c4d\u0c28\u0c41 \u0c2a\u0c4d\u0c30\u0c3e\u0c30\u0c02\u0c2d\u0c3f\u0c38\u0c4d\u0c24\u0c41\u0c02\u0c26\u0c3f.", + "cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41", + "confirm": "\u0c27\u0c4d\u0c30\u0c41\u0c35\u0c2a\u0c30\u0c1a\u0c41" + }, + "settingsModal": { + "settings": "\u0c38\u0c46\u0c1f\u0c4d\u0c1f\u0c3f\u0c02\u0c17\u0c4d \u0c32\u0c41", + "expandMessages": "\u0c38\u0c02\u0c26\u0c47\u0c36\u0c3e\u0c32\u0c28\u0c41 \u0c35\u0c3f\u0c38\u0c4d\u0c24\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", + "hideChainOfThought": "\u0c06\u0c32\u0c4b\u0c1a\u0c28\u0c3e \u0c17\u0c4a\u0c32\u0c41\u0c38\u0c41\u0c28\u0c41 \u0c26\u0c3e\u0c1a\u0c02\u0c21\u0c3f", + "darkMode": "\u0c21\u0c3e\u0c30\u0c4d\u0c15\u0c4d \u0c2e\u0c4b\u0c21\u0c4d" + }, + "detailsButton": { + "using": "\u0c09\u0c2a\u0c2f\u0c4b\u0c17\u0c3f\u0c02\u0c1a\u0c21\u0c02", + "running": "\u0c30\u0c28\u0c4d\u0c28\u0c3f\u0c02\u0c17\u0c4d", + "took_one": "{{count}} \u0c05\u0c21\u0c41\u0c17\u0c41 \u0c35\u0c47\u0c38\u0c3f\u0c02\u0c26\u0c3f", + "took_other": "{{count}} \u0c05\u0c21\u0c41\u0c17\u0c41\u0c32\u0c41 \u0c35\u0c47\u0c38\u0c3f\u0c02\u0c26\u0c3f" + }, + "auth": { + "authLogin": { + "title": "\u0c2f\u0c3e\u0c2a\u0c4d \u0c2f\u0c3e\u0c15\u0c4d\u0c38\u0c46\u0c38\u0c4d \u0c1a\u0c47\u0c38\u0c41\u0c15\u0c4b\u0c35\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c32\u0c3e\u0c17\u0c3f\u0c28\u0c4d \u0c05\u0c35\u0c4d\u0c35\u0c02\u0c21\u0c3f.", + "form": { + "email": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c1a\u0c3f\u0c30\u0c41\u0c28\u0c3e\u0c2e\u0c3e", + "password": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d", + "noAccount": "\u0c2e\u0c40\u0c15\u0c41 \u0c05\u0c15\u0c4c\u0c02\u0c1f\u0c4d \u0c32\u0c47\u0c26\u0c3e?", + "alreadyHaveAccount": "\u0c07\u0c2a\u0c4d\u0c2a\u0c1f\u0c3f\u0c15\u0c47 \u0c16\u0c3e\u0c24\u0c3e \u0c09\u0c02\u0c26\u0c3e?", + "signup": "\u0c38\u0c46\u0c56\u0c28\u0c4d \u0c05\u0c2a\u0c4d", + "signin": "\u0c38\u0c46\u0c56\u0c28\u0c4d \u0c07\u0c28\u0c4d", + "or": "\u0c32\u0c47\u0c26\u0c3e", + "continue": "\u0c15\u0c4a\u0c28\u0c38\u0c3e\u0c17\u0c41", + "forgotPassword": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c2e\u0c30\u0c4d\u0c1a\u0c3f\u0c2a\u0c4b\u0c2f\u0c3e\u0c30\u0c3e?", + "passwordMustContain": "\u0c2e\u0c40 \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c32\u0c4b \u0c07\u0c35\u0c3f \u0c09\u0c02\u0c21\u0c3e\u0c32\u0c3f:", + "emailRequired": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c05\u0c28\u0c47\u0c26\u0c3f \u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 \u0c2b\u0c40\u0c32\u0c4d\u0c21\u0c4d", + "passwordRequired": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c05\u0c28\u0c47\u0c26\u0c3f \u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 \u0c2b\u0c40\u0c32\u0c4d\u0c21\u0c4d" + }, + "error": { + "default": "\u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c02 \u0c38\u0c3e\u0c27\u0c4d\u0c2f\u0c02 \u0c15\u0c3e\u0c26\u0c41.", + "signin": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "oauthsignin": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "redirect_uri_mismatch": "\u0c30\u0c40\u0c21\u0c48\u0c30\u0c46\u0c15\u0c4d\u0c1f\u0c4d URI \u0c13\u0c2f\u0c42\u0c24\u0c4d \u0c2f\u0c3e\u0c2a\u0c4d \u0c15\u0c3e\u0c28\u0c4d\u0c2b\u0c3f\u0c17\u0c30\u0c47\u0c37\u0c28\u0c4d \u0c15\u0c41 \u0c38\u0c30\u0c3f\u0c2a\u0c4b\u0c32\u0c21\u0c02 \u0c32\u0c47\u0c26\u0c41.", + "oauthcallbackerror": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "oauthcreateaccount": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "emailcreateaccount": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "callback": "\u0c35\u0c47\u0c30\u0c4a\u0c15 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.", + "oauthaccountnotlinked": "\u0c2e\u0c40 \u0c17\u0c41\u0c30\u0c4d\u0c24\u0c3f\u0c02\u0c2a\u0c41\u0c28\u0c41 \u0c27\u0c43\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f, \u0c2e\u0c40\u0c30\u0c41 \u0c2e\u0c4a\u0c26\u0c1f \u0c09\u0c2a\u0c2f\u0c4b\u0c17\u0c3f\u0c02\u0c1a\u0c3f\u0c28 \u0c05\u0c26\u0c47 \u0c16\u0c3e\u0c24\u0c3e\u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f.", + "emailsignin": "\u0c07-\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c2a\u0c02\u0c2a\u0c21\u0c02 \u0c38\u0c3e\u0c27\u0c4d\u0c2f\u0c02 \u0c15\u0c3e\u0c26\u0c41.", + "emailverify": "\u0c26\u0c2f\u0c1a\u0c47\u0c38\u0c3f \u0c2e\u0c40 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c28\u0c3f \u0c27\u0c43\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f, \u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c2a\u0c02\u0c2a\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f.", + "credentialssignin": "\u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c35\u0c3f\u0c2b\u0c32\u0c2e\u0c48\u0c02\u0c26\u0c3f. \u0c2e\u0c40\u0c30\u0c41 \u0c05\u0c02\u0c26\u0c3f\u0c02\u0c1a\u0c3f\u0c28 \u0c35\u0c3f\u0c35\u0c30\u0c3e\u0c32\u0c41 \u0c38\u0c30\u0c3f\u0c17\u0c4d\u0c17\u0c3e \u0c09\u0c28\u0c4d\u0c28\u0c3e\u0c2f\u0c4b \u0c32\u0c47\u0c26\u0c4b \u0c1a\u0c46\u0c15\u0c4d \u0c1a\u0c47\u0c38\u0c41\u0c15\u0c4b\u0c02\u0c21\u0c3f.", + "sessionrequired": "\u0c08 \u0c2a\u0c47\u0c1c\u0c40\u0c28\u0c3f \u0c2f\u0c3e\u0c15\u0c4d\u0c38\u0c46\u0c38\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c02 \u0c15\u0c4a\u0c30\u0c15\u0c41 \u0c26\u0c2f\u0c1a\u0c47\u0c38\u0c3f \u0c38\u0c48\u0c28\u0c4d \u0c07\u0c28\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f." + } + }, + "authVerifyEmail": { + "almostThere": "\u0c2e\u0c40\u0c30\u0c41 \u0c26\u0c3e\u0c26\u0c3e\u0c2a\u0c41 \u0c05\u0c15\u0c4d\u0c15\u0c21\u0c47 \u0c09\u0c28\u0c4d\u0c28\u0c3e\u0c30\u0c41! \u0c2e\u0c47\u0c2e\u0c41 \u0c26\u0c40\u0c28\u0c3f\u0c15\u0c3f \u0c12\u0c15 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c2a\u0c02\u0c2a\u0c3e\u0c2e\u0c41 ", + "verifyEmailLink": "\u0c2e\u0c40 \u0c38\u0c48\u0c28\u0c4d \u0c05\u0c2a\u0c4d \u0c2a\u0c42\u0c30\u0c4d\u0c24\u0c3f \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c26\u0c2f\u0c1a\u0c47\u0c38\u0c3f \u0c06 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c32\u0c4b\u0c28\u0c3f \u0c32\u0c3f\u0c02\u0c15\u0c4d \u0c2a\u0c48 \u0c15\u0c4d\u0c32\u0c3f\u0c15\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f.", + "didNotReceive": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c28\u0c3f \u0c15\u0c28\u0c41\u0c17\u0c4a\u0c28\u0c32\u0c47\u0c15\u0c2a\u0c4b\u0c2f\u0c3e\u0c30\u0c3e?", + "resendEmail": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c28\u0c3f \u0c24\u0c3f\u0c30\u0c3f\u0c17\u0c3f \u0c2a\u0c02\u0c2a\u0c02\u0c21\u0c3f", + "goBack": "\u0c35\u0c46\u0c28\u0c15\u0c4d\u0c15\u0c3f \u0c35\u0c46\u0c33\u0c4d\u0c33\u0c41", + "emailSent": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c35\u0c3f\u0c1c\u0c2f\u0c35\u0c02\u0c24\u0c02\u0c17\u0c3e \u0c2a\u0c02\u0c2a\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f.", + "verifyEmail": "\u0c2e\u0c40 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c1a\u0c3f\u0c30\u0c41\u0c28\u0c3e\u0c2e\u0c3e\u0c28\u0c41 \u0c27\u0c43\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f" + }, + "providerButton": { + "continue": "{{provider}} \u0c24\u0c4b \u0c15\u0c4a\u0c28\u0c38\u0c3e\u0c17\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", + "signup": "{{provider}} \u0c24\u0c4b \u0c38\u0c48\u0c28\u0c4d \u0c05\u0c2a\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f" + }, + "authResetPassword": { + "newPasswordRequired": "\u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c05\u0c28\u0c47\u0c26\u0c3f \u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 \u0c2b\u0c40\u0c32\u0c4d\u0c21\u0c4d", + "passwordsMustMatch": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c32\u0c41 \u0c24\u0c2a\u0c4d\u0c2a\u0c28\u0c3f\u0c38\u0c30\u0c3f\u0c17\u0c3e \u0c38\u0c30\u0c3f\u0c2a\u0c4b\u0c32\u0c3e\u0c32\u0c3f", + "confirmPasswordRequired": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c05\u0c28\u0c47\u0c26\u0c3f \u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 \u0c2b\u0c40\u0c32\u0c4d\u0c21\u0c4d \u0c05\u0c28\u0c3f \u0c27\u0c43\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", + "newPassword": "\u0c15\u0c4a\u0c24\u0c4d\u0c24 \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d", + "confirmPassword": "\u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c28\u0c41 \u0c27\u0c43\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", + "resetPassword": "\u0c30\u0c40\u0c38\u0c46\u0c1f\u0c4d \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d" + }, + "authForgotPassword": { + "email": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c1a\u0c3f\u0c30\u0c41\u0c28\u0c3e\u0c2e\u0c3e", + "emailRequired": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c05\u0c28\u0c47\u0c26\u0c3f \u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 \u0c2b\u0c40\u0c32\u0c4d\u0c21\u0c4d", + "emailSent": "\u0c2e\u0c40 \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c30\u0c40\u0c38\u0c46\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c38\u0c42\u0c1a\u0c28\u0c32 \u0c15\u0c4a\u0c30\u0c15\u0c41 \u0c26\u0c2f\u0c1a\u0c47\u0c38\u0c3f {{email}} \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c1a\u0c3f\u0c30\u0c41\u0c28\u0c3e\u0c2e\u0c3e\u0c28\u0c41 \u0c24\u0c28\u0c3f\u0c16\u0c40 \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f.", + "enterEmail": "\u0c2e\u0c40 \u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c1a\u0c3f\u0c30\u0c41\u0c28\u0c3e\u0c2e\u0c3e\u0c28\u0c41 \u0c28\u0c2e\u0c4b\u0c26\u0c41 \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f \u0c2e\u0c30\u0c3f\u0c2f\u0c41 \u0c2e\u0c40 \u0c2a\u0c3e\u0c38\u0c4d \u0c35\u0c30\u0c4d\u0c21\u0c4d \u0c28\u0c41 \u0c30\u0c40\u0c38\u0c46\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c2e\u0c47\u0c2e\u0c41 \u0c2e\u0c40\u0c15\u0c41 \u0c38\u0c42\u0c1a\u0c28\u0c32\u0c41 \u0c2a\u0c02\u0c2a\u0c41\u0c24\u0c3e\u0c2e\u0c41.", + "resendEmail": "\u0c07\u0c2e\u0c46\u0c2f\u0c3f\u0c32\u0c4d \u0c28\u0c3f \u0c24\u0c3f\u0c30\u0c3f\u0c17\u0c3f \u0c2a\u0c02\u0c2a\u0c02\u0c21\u0c3f", + "continue": "\u0c15\u0c4a\u0c28\u0c38\u0c3e\u0c17\u0c41", + "goBack": "\u0c35\u0c46\u0c28\u0c15\u0c4d\u0c15\u0c3f \u0c35\u0c46\u0c33\u0c4d\u0c33\u0c41" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u0c1a\u0c30\u0c3f\u0c24\u0c4d\u0c30\u0c28\u0c41 \u0c1a\u0c42\u0c2a\u0c3f\u0c02\u0c1a\u0c41", + "lastInputs": "\u0c1a\u0c3f\u0c35\u0c30\u0c3f \u0c07\u0c28\u0c4d \u0c2a\u0c41\u0c1f\u0c4d \u0c32\u0c41", + "noInputs": "\u0c05\u0c02\u0c24 \u0c16\u0c3e\u0c33\u0c40\u0c17\u0c3e...", + "loading": "\u0c32\u0c4b\u0c21\u0c3f\u0c02\u0c17\u0c4d..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u0c2e\u0c40 \u0c38\u0c02\u0c26\u0c47\u0c36\u0c3e\u0c28\u0c4d\u0c28\u0c3f \u0c07\u0c15\u0c4d\u0c15\u0c21 \u0c1f\u0c48\u0c2a\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f..." + }, + "speechButton": { + "start": "\u0c30\u0c3f\u0c15\u0c3e\u0c30\u0c4d\u0c21\u0c3f\u0c02\u0c17\u0c4d \u0c2a\u0c4d\u0c30\u0c3e\u0c30\u0c02\u0c2d\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", + "stop": "\u0c30\u0c3f\u0c15\u0c3e\u0c30\u0c4d\u0c21\u0c3f\u0c02\u0c17\u0c4d \u0c06\u0c2a\u0c02\u0c21\u0c3f" + }, + "SubmitButton": { + "sendMessage": "\u0c38\u0c02\u0c26\u0c47\u0c36\u0c02 \u0c2a\u0c02\u0c2a\u0c41", + "stopTask": "\u0c38\u0c4d\u0c1f\u0c3e\u0c2a\u0c4d \u0c1f\u0c3e\u0c38\u0c4d\u0c15\u0c4d" + }, + "UploadButton": { + "attachFiles": "\u0c2b\u0c48\u0c33\u0c4d\u0c32\u0c28\u0c41 \u0c1c\u0c4b\u0c21\u0c3f\u0c02\u0c1a\u0c41" + }, + "waterMark": { + "text": "\u0c26\u0c40\u0c28\u0c3f\u0c24\u0c4b \u0c28\u0c3f\u0c30\u0c4d\u0c2e\u0c3f\u0c02\u0c1a\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f" + } + }, + "Messages": { + "index": { + "running": "\u0c30\u0c28\u0c4d\u0c28\u0c3f\u0c02\u0c17\u0c4d", + "executedSuccessfully": "\u0c35\u0c3f\u0c1c\u0c2f\u0c35\u0c02\u0c24\u0c02\u0c17\u0c3e \u0c05\u0c2e\u0c32\u0c41 \u0c1a\u0c47\u0c2f\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f", + "failed": "\u0c35\u0c3f\u0c2b\u0c32\u0c2e\u0c48\u0c02\u0c26\u0c3f", + "feedbackUpdated": "\u0c2b\u0c40\u0c21\u0c4d \u0c2c\u0c4d\u0c2f\u0c3e\u0c15\u0c4d \u0c05\u0c2a\u0c4d \u0c21\u0c47\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f", + "updating": "\u0c05\u0c2a\u0c4d \u0c21\u0c47\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c02" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u0c2e\u0c40 \u0c2b\u0c48\u0c33\u0c4d\u0c32\u0c28\u0c41 \u0c07\u0c15\u0c4d\u0c15\u0c21 \u0c21\u0c4d\u0c30\u0c3e\u0c2a\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f" + }, + "index": { + "failedToUpload": "\u0c05\u0c2a\u0c4d \u0c32\u0c4b\u0c21\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c02 \u0c35\u0c3f\u0c2b\u0c32\u0c2e\u0c48\u0c02\u0c26\u0c3f", + "cancelledUploadOf": "\u0c30\u0c26\u0c4d\u0c26\u0c41 \u0c1a\u0c47\u0c38\u0c3f\u0c28 \u0c05\u0c2a\u0c4d \u0c32\u0c4b\u0c21\u0c4d", + "couldNotReachServer": "\u0c38\u0c30\u0c4d\u0c35\u0c30\u0c4d \u0c15\u0c41 \u0c1a\u0c47\u0c30\u0c41\u0c15\u0c4b\u0c35\u0c21\u0c02 \u0c38\u0c3e\u0c27\u0c4d\u0c2f\u0c02 \u0c15\u0c3e\u0c32\u0c47\u0c26\u0c41", + "continuingChat": "\u0c2e\u0c41\u0c28\u0c41\u0c2a\u0c1f\u0c3f \u0c1a\u0c3e\u0c1f\u0c4d \u0c28\u0c41 \u0c15\u0c4a\u0c28\u0c38\u0c3e\u0c17\u0c3f\u0c02\u0c1a\u0c21\u0c02" + }, + "settings": { + "settingsPanel": "\u0c38\u0c46\u0c1f\u0c4d\u0c1f\u0c3f\u0c02\u0c17\u0c4d\u0c38\u0c4d \u0c2a\u0c4d\u0c2f\u0c3e\u0c28\u0c46\u0c32\u0c4d", + "reset": "\u0c30\u0c40\u0c38\u0c46\u0c1f\u0c4d", + "cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41", + "confirm": "\u0c27\u0c4d\u0c30\u0c41\u0c35\u0c2a\u0c30\u0c1a\u0c41" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u0c2b\u0c40\u0c21\u0c4d \u0c2c\u0c4d\u0c2f\u0c3e\u0c15\u0c4d: \u0c05\u0c28\u0c4d\u0c28\u0c40", + "feedbackPositive": "\u0c2b\u0c40\u0c21\u0c4d \u0c2c\u0c4d\u0c2f\u0c3e\u0c15\u0c4d: \u0c2a\u0c3e\u0c1c\u0c3f\u0c1f\u0c3f\u0c35\u0c4d", + "feedbackNegative": "\u0c2b\u0c40\u0c21\u0c4d \u0c2c\u0c4d\u0c2f\u0c3e\u0c15\u0c4d: \u0c28\u0c46\u0c17\u0c46\u0c1f\u0c3f\u0c35\u0c4d" + }, + "SearchBar": { + "search": "\u0c35\u0c46\u0c24\u0c41\u0c15\u0c41" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u0c07\u0c26\u0c3f \u0c25\u0c4d\u0c30\u0c46\u0c21\u0c4d\u0c24\u0c4b \u0c2a\u0c3e\u0c1f\u0c41 \u0c26\u0c3e\u0c28\u0c3f \u0c38\u0c02\u0c26\u0c47\u0c36\u0c3e\u0c32\u0c41 \u0c2e\u0c30\u0c3f\u0c2f\u0c41 \u0c0e\u0c32\u0c3f\u0c2e\u0c46\u0c02\u0c1f\u0c4d\u0c32\u0c28\u0c41 \u0c24\u0c4a\u0c32\u0c17\u0c3f\u0c38\u0c4d\u0c24\u0c41\u0c02\u0c26\u0c3f.", + "cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41", + "confirm": "\u0c27\u0c4d\u0c30\u0c41\u0c35\u0c2a\u0c30\u0c1a\u0c41", + "deletingChat": "\u0c1a\u0c3e\u0c1f\u0c4d \u0c28\u0c41 \u0c21\u0c3f\u0c32\u0c40\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c02", + "chatDeleted": "\u0c1a\u0c3e\u0c1f\u0c4d \u0c21\u0c3f\u0c32\u0c40\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f" + }, + "index": { + "pastChats": "\u0c17\u0c24 \u0c1a\u0c3e\u0c1f\u0c4d \u0c32\u0c41" + }, + "ThreadList": { + "empty": "\u0c16\u0c3e\u0c33\u0c40...", + "today": "\u0c08 \u0c30\u0c4b\u0c1c\u0c41", + "yesterday": "\u0c28\u0c3f\u0c28\u0c4d\u0c28", + "previous7days": "\u0c2e\u0c41\u0c28\u0c41\u0c2a\u0c1f\u0c3f 7 \u0c30\u0c4b\u0c1c\u0c41\u0c32\u0c41", + "previous30days": "\u0c2e\u0c41\u0c28\u0c41\u0c2a\u0c1f\u0c3f 30 \u0c30\u0c4b\u0c1c\u0c41\u0c32\u0c41" + }, + "TriggerButton": { + "closeSidebar": "\u0c15\u0c4d\u0c32\u0c4b\u0c1c\u0c4d \u0c38\u0c48\u0c21\u0c4d \u0c2c\u0c3e\u0c30\u0c4d", + "openSidebar": "\u0c13\u0c2a\u0c46\u0c28\u0c4d \u0c38\u0c48\u0c21\u0c4d \u0c2c\u0c3e\u0c30\u0c4d" + } + }, + "Thread": { + "backToChat": "\u0c1a\u0c3e\u0c1f\u0c4d \u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f \u0c24\u0c3f\u0c30\u0c3f\u0c17\u0c3f \u0c35\u0c46\u0c33\u0c4d\u0c32\u0c02\u0c21\u0c3f", + "chatCreatedOn": "\u0c08 \u0c1a\u0c3e\u0c1f\u0c4d \u0c26\u0c40\u0c28\u0c3f\u0c32\u0c4b \u0c38\u0c43\u0c37\u0c4d\u0c1f\u0c3f\u0c02\u0c1a\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f" + } + }, + "header": { + "chat": "\u0c2e\u0c41\u0c1a\u0c4d\u0c1a\u0c1f\u0c3f\u0c02\u0c1a\u0c41", + "readme": "Readme" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u0c2a\u0c4d\u0c30\u0c4a\u0c35\u0c48\u0c21\u0c30\u0c4d\u0c32\u0c28\u0c41 \u0c2a\u0c4a\u0c02\u0c26\u0c21\u0c02\u0c32\u0c4b \u0c35\u0c3f\u0c2b\u0c32\u0c2e\u0c48\u0c02\u0c26\u0c3f:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u0c35\u0c3f\u0c1c\u0c2f\u0c35\u0c02\u0c24\u0c02\u0c17\u0c3e \u0c38\u0c47\u0c35\u0c4d \u0c1a\u0c47\u0c2f\u0c2c\u0c21\u0c3f\u0c02\u0c26\u0c3f", + "requiredApiKeys": "\u0c05\u0c35\u0c38\u0c30\u0c2e\u0c48\u0c28 API \u0c15\u0c40\u0c32\u0c41", + "requiredApiKeysInfo": "\u0c08 \u0c2f\u0c3e\u0c2a\u0c4d \u0c09\u0c2a\u0c2f\u0c4b\u0c17\u0c3f\u0c02\u0c1a\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f, \u0c08 \u0c15\u0c4d\u0c30\u0c3f\u0c02\u0c26\u0c3f API \u0c15\u0c40\u0c32\u0c41 \u0c05\u0c35\u0c38\u0c30\u0c02 \u0c05\u0c35\u0c41\u0c24\u0c3e\u0c2f\u0c3f. \u0c15\u0c40\u0c32\u0c41 \u0c2e\u0c40 \u0c2a\u0c30\u0c3f\u0c15\u0c30\u0c02 \u0c2f\u0c4a\u0c15\u0c4d\u0c15 \u0c38\u0c4d\u0c25\u0c3e\u0c28\u0c3f\u0c15 \u0c38\u0c4d\u0c1f\u0c4b\u0c30\u0c47\u0c1c\u0c40\u0c32\u0c4b \u0c28\u0c3f\u0c32\u0c4d\u0c35 \u0c1a\u0c47\u0c2f\u0c2c\u0c21\u0c24\u0c3e\u0c2f\u0c3f." + }, + "Page": { + "notPartOfProject": "\u0c2e\u0c40\u0c30\u0c41 \u0c08 \u0c2a\u0c4d\u0c30\u0c3e\u0c1c\u0c46\u0c15\u0c4d\u0c1f\u0c41\u0c32\u0c4b \u0c2d\u0c3e\u0c17\u0c02 \u0c15\u0c3e\u0c26\u0c41." + }, + "ResumeButton": { + "resumeChat": "\u0c30\u0c46\u0c1c\u0c4d\u0c2f\u0c42\u0c2e\u0c4d \u0c1a\u0c3e\u0c1f\u0c4d" + } + } +} \ No newline at end of file diff --git a/examples/chatbot/.chainlit/translations/zh-CN.json b/examples/chatbot/.chainlit/translations/zh-CN.json new file mode 100644 index 000000000..b0c5f5494 --- /dev/null +++ b/examples/chatbot/.chainlit/translations/zh-CN.json @@ -0,0 +1,229 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "\u8bbe\u7f6e", + "settingsKey": "S", + "APIKeys": "API \u5bc6\u94a5", + "logout": "\u767b\u51fa" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "\u65b0\u5efa\u5bf9\u8bdd" + }, + "tasklist": { + "TaskList": { + "title": "\ud83d\uddd2\ufe0f \u4efb\u52a1\u5217\u8868", + "loading": "\u52a0\u8f7d\u4e2d...", + "error": "\u53d1\u751f\u9519\u8bef" + } + }, + "attachments": { + "cancelUpload": "\u53d6\u6d88\u4e0a\u4f20", + "removeAttachment": "\u79fb\u9664\u9644\u4ef6" + }, + "newChatDialog": { + "createNewChat": "\u521b\u5efa\u65b0\u5bf9\u8bdd\uff1f", + "clearChat": "\u8fd9\u5c06\u6e05\u9664\u5f53\u524d\u6d88\u606f\u5e76\u5f00\u59cb\u65b0\u7684\u5bf9\u8bdd\u3002", + "cancel": "\u53d6\u6d88", + "confirm": "\u786e\u8ba4" + }, + "settingsModal": { + "settings": "\u8bbe\u7f6e", + "expandMessages": "\u5c55\u5f00\u6d88\u606f", + "hideChainOfThought": "\u9690\u85cf\u601d\u8003\u94fe", + "darkMode": "\u6697\u8272\u6a21\u5f0f" + }, + "detailsButton": { + "using": "\u4f7f\u7528", + "used": "\u5df2\u7528" + }, + "auth": { + "authLogin": { + "title": "\u767b\u5f55\u4ee5\u8bbf\u95ee\u5e94\u7528\u3002", + "form": { + "email": "\u7535\u5b50\u90ae\u7bb1\u5730\u5740", + "password": "\u5bc6\u7801", + "noAccount": "\u6ca1\u6709\u8d26\u6237\uff1f", + "alreadyHaveAccount": "\u5df2\u6709\u8d26\u6237\uff1f", + "signup": "\u6ce8\u518c", + "signin": "\u767b\u5f55", + "or": "\u6216\u8005", + "continue": "\u7ee7\u7eed", + "forgotPassword": "\u5fd8\u8bb0\u5bc6\u7801\uff1f", + "passwordMustContain": "\u60a8\u7684\u5bc6\u7801\u5fc5\u987b\u5305\u542b\uff1a", + "emailRequired": "\u7535\u5b50\u90ae\u7bb1\u662f\u5fc5\u586b\u9879", + "passwordRequired": "\u5bc6\u7801\u662f\u5fc5\u586b\u9879" + }, + "error": { + "default": "\u65e0\u6cd5\u767b\u5f55\u3002", + "signin": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "oauthsignin": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "redirect_uri_mismatch": "\u91cd\u5b9a\u5411URI\u4e0eOAuth\u5e94\u7528\u914d\u7f6e\u4e0d\u5339\u914d\u3002", + "oauthcallbackerror": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "oauthcreateaccount": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "emailcreateaccount": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "callback": "\u5c1d\u8bd5\u4f7f\u7528\u4e0d\u540c\u7684\u8d26\u6237\u767b\u5f55\u3002", + "oauthaccountnotlinked": "\u4e3a\u4e86\u9a8c\u8bc1\u60a8\u7684\u8eab\u4efd\uff0c\u8bf7\u4f7f\u7528\u6700\u521d\u4f7f\u7528\u7684\u540c\u4e00\u8d26\u6237\u767b\u5f55\u3002", + "emailsignin": "\u65e0\u6cd5\u53d1\u9001\u90ae\u4ef6\u3002", + "emailverify": "\u8bf7\u9a8c\u8bc1\u60a8\u7684\u7535\u5b50\u90ae\u4ef6\uff0c\u5df2\u53d1\u9001\u4e00\u5c01\u65b0\u90ae\u4ef6\u3002", + "credentialssignin": "\u767b\u5f55\u5931\u8d25\u3002\u8bf7\u68c0\u67e5\u60a8\u63d0\u4f9b\u7684\u8be6\u7ec6\u4fe1\u606f\u662f\u5426\u6b63\u786e\u3002", + "sessionrequired": "\u8bf7\u767b\u5f55\u4ee5\u8bbf\u95ee\u6b64\u9875\u9762\u3002" + } + }, + "authVerifyEmail": { + "almostThere": "\u60a8\u5feb\u6210\u529f\u4e86\uff01\u6211\u4eec\u5df2\u5411 ", + "verifyEmailLink": "\u8bf7\u5355\u51fb\u8be5\u90ae\u4ef6\u4e2d\u7684\u94fe\u63a5\u4ee5\u5b8c\u6210\u6ce8\u518c\u3002", + "didNotReceive": "\u6ca1\u627e\u5230\u90ae\u4ef6\uff1f", + "resendEmail": "\u91cd\u65b0\u53d1\u9001\u90ae\u4ef6", + "goBack": "\u8fd4\u56de", + "emailSent": "\u90ae\u4ef6\u5df2\u6210\u529f\u53d1\u9001\u3002", + "verifyEmail": "\u9a8c\u8bc1\u60a8\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740" + }, + "providerButton": { + "continue": "\u4f7f\u7528{{provider}}\u7ee7\u7eed", + "signup": "\u4f7f\u7528{{provider}}\u6ce8\u518c" + }, + "authResetPassword": { + "newPasswordRequired": "\u65b0\u5bc6\u7801\u662f\u5fc5\u586b\u9879", + "passwordsMustMatch": "\u5bc6\u7801\u5fc5\u987b\u4e00\u81f4", + "confirmPasswordRequired": "\u786e\u8ba4\u5bc6\u7801\u662f\u5fc5\u586b\u9879", + "newPassword": "\u65b0\u5bc6\u7801", + "confirmPassword": "\u786e\u8ba4\u5bc6\u7801", + "resetPassword": "\u91cd\u7f6e\u5bc6\u7801" + }, + "authForgotPassword": { + "email": "\u7535\u5b50\u90ae\u7bb1\u5730\u5740", + "emailRequired": "\u7535\u5b50\u90ae\u7bb1\u662f\u5fc5\u586b\u9879", + "emailSent": "\u8bf7\u68c0\u67e5\u7535\u5b50\u90ae\u7bb1{{email}}\u4ee5\u83b7\u53d6\u91cd\u7f6e\u5bc6\u7801\u7684\u6307\u793a\u3002", + "enterEmail": "\u8bf7\u8f93\u5165\u60a8\u7684\u7535\u5b50\u90ae\u7bb1\u5730\u5740\uff0c\u6211\u4eec\u5c06\u53d1\u9001\u91cd\u7f6e\u5bc6\u7801\u7684\u6307\u793a\u3002", + "resendEmail": "\u91cd\u65b0\u53d1\u9001\u90ae\u4ef6", + "continue": "\u7ee7\u7eed", + "goBack": "\u8fd4\u56de" + } + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "showHistory": "\u663e\u793a\u5386\u53f2", + "lastInputs": "\u6700\u540e\u8f93\u5165", + "noInputs": "\u5982\u6b64\u7a7a\u65f7...", + "loading": "\u52a0\u8f7d\u4e2d..." + } + }, + "inputBox": { + "input": { + "placeholder": "\u5728\u8fd9\u91cc\u8f93\u5165\u60a8\u7684\u6d88\u606f..." + }, + "speechButton": { + "start": "\u5f00\u59cb\u5f55\u97f3", + "stop": "\u505c\u6b62\u5f55\u97f3" + }, + "SubmitButton": { + "sendMessage": "\u53d1\u9001\u6d88\u606f", + "stopTask": "\u505c\u6b62\u4efb\u52a1" + }, + "UploadButton": { + "attachFiles": "\u9644\u52a0\u6587\u4ef6" + }, + "waterMark": { + "text": "\u4f7f\u7528" + } + }, + "Messages": { + "index": { + "running": "\u8fd0\u884c\u4e2d", + "executedSuccessfully": "\u6267\u884c\u6210\u529f", + "failed": "\u5931\u8d25", + "feedbackUpdated": "\u53cd\u9988\u66f4\u65b0", + "updating": "\u6b63\u5728\u66f4\u65b0" + } + }, + "dropScreen": { + "dropYourFilesHere": "\u5728\u8fd9\u91cc\u62d6\u653e\u60a8\u7684\u6587\u4ef6" + }, + "index": { + "failedToUpload": "\u4e0a\u4f20\u5931\u8d25", + "cancelledUploadOf": "\u53d6\u6d88\u4e0a\u4f20", + "couldNotReachServer": "\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668", + "continuingChat": "\u7ee7\u7eed\u4e4b\u524d\u7684\u5bf9\u8bdd" + }, + "settings": { + "settingsPanel": "\u8bbe\u7f6e\u9762\u677f", + "reset": "\u91cd\u7f6e", + "cancel": "\u53d6\u6d88", + "confirm": "\u786e\u8ba4" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "\u53cd\u9988\uff1a\u5168\u90e8", + "feedbackPositive": "\u53cd\u9988\uff1a\u6b63\u9762", + "feedbackNegative": "\u53cd\u9988\uff1a\u8d1f\u9762" + }, + "SearchBar": { + "search": "\u641c\u7d22" + } + }, + "DeleteThreadButton": { + "confirmMessage": "\u8fd9\u5c06\u5220\u9664\u7ebf\u7a0b\u53ca\u5176\u6d88\u606f\u548c\u5143\u7d20\u3002", + "cancel": "\u53d6\u6d88", + "confirm": "\u786e\u8ba4", + "deletingChat": "\u5220\u9664\u5bf9\u8bdd", + "chatDeleted": "\u5bf9\u8bdd\u5df2\u5220\u9664" + }, + "index": { + "pastChats": "\u8fc7\u5f80\u5bf9\u8bdd" + }, + "ThreadList": { + "empty": "\u7a7a\u7684...", + "today": "\u4eca\u5929", + "yesterday": "\u6628\u5929", + "previous7days": "\u524d7\u5929", + "previous30days": "\u524d30\u5929" + }, + "TriggerButton": { + "closeSidebar": "\u5173\u95ed\u4fa7\u8fb9\u680f", + "openSidebar": "\u6253\u5f00\u4fa7\u8fb9\u680f" + } + }, + "Thread": { + "backToChat": "\u8fd4\u56de\u5bf9\u8bdd", + "chatCreatedOn": "\u6b64\u5bf9\u8bdd\u521b\u5efa\u4e8e" + } + }, + "header": { + "chat": "\u5bf9\u8bdd", + "readme": "\u8bf4\u660e" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "\u83b7\u53d6\u63d0\u4f9b\u8005\u5931\u8d25:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "\u4fdd\u5b58\u6210\u529f", + "requiredApiKeys": "\u5fc5\u9700\u7684API\u5bc6\u94a5", + "requiredApiKeysInfo": "\u8981\u4f7f\u7528\u6b64\u5e94\u7528\uff0c\u9700\u8981\u4ee5\u4e0bAPI\u5bc6\u94a5\u3002\u8fd9\u4e9b\u5bc6\u94a5\u5b58\u50a8\u5728\u60a8\u7684\u8bbe\u5907\u672c\u5730\u5b58\u50a8\u4e2d\u3002" + }, + "Page": { + "notPartOfProject": "\u60a8\u4e0d\u662f\u6b64\u9879\u76ee\u7684\u4e00\u90e8\u5206\u3002" + }, + "ResumeButton": { + "resumeChat": "\u6062\u590d\u5bf9\u8bdd" + } + } +} \ No newline at end of file diff --git a/backend/core/.gitignore b/examples/chatbot/.gitignore similarity index 100% rename from backend/core/.gitignore rename to examples/chatbot/.gitignore diff --git a/backend/worker/.python-version b/examples/chatbot/.python-version similarity index 100% rename from backend/worker/.python-version rename to examples/chatbot/.python-version diff --git a/backend/core/examples/chatbot/README.md b/examples/chatbot/README.md similarity index 91% rename from backend/core/examples/chatbot/README.md rename to examples/chatbot/README.md index f78320cb6..3cfa916ae 100644 --- a/backend/core/examples/chatbot/README.md +++ b/examples/chatbot/README.md @@ -8,12 +8,12 @@ This example demonstrates how to create a simple chatbot using Quivr and Chainli ## Installation -1. Clone the repository or navigate to the `backend/core/examples/chatbot` directory. +1. Clone the repository or navigate to the `core/examples/chatbot` directory. 2. Install the required dependencies: ``` - pip install -r requirements.txt + pip install -r requirements.lock ``` ## Running the Chatbot diff --git a/backend/core/examples/chatbot/chainlit.md b/examples/chatbot/chainlit.md similarity index 100% rename from backend/core/examples/chatbot/chainlit.md rename to examples/chatbot/chainlit.md diff --git a/backend/core/examples/chatbot/main.py b/examples/chatbot/main.py similarity index 100% rename from backend/core/examples/chatbot/main.py rename to examples/chatbot/main.py diff --git a/examples/chatbot/pyproject.toml b/examples/chatbot/pyproject.toml new file mode 100644 index 000000000..c1d350a0f --- /dev/null +++ b/examples/chatbot/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "chatbot" +version = "0.1.0" +description = "Add your description here" +authors = [ + { name = "Stan Girard", email = "stan@quivr.app" } +] +dependencies = [ + "quivr-core[all]>=0.0.18", + "chainlit>=1.2.0", + "sqlmodel>=0.0.22", + "langchain-cohere>=0.2.0", +] +readme = "README.md" +requires-python = ">= 3.11" + +[tool.rye] +managed = true +virtual = true +dev-dependencies = [] diff --git a/backend/requirements-dev.lock b/examples/chatbot/requirements-dev.lock similarity index 52% rename from backend/requirements-dev.lock rename to examples/chatbot/requirements-dev.lock index 2ae36b678..8a856b6cc 100644 --- a/backend/requirements-dev.lock +++ b/examples/chatbot/requirements-dev.lock @@ -3,185 +3,116 @@ # # last locked with the following flags: # pre: false -# features: ["quivr-core/all"] +# features: [] # all-features: false -# with-sources: true +# with-sources: false # generate-hashes: false -# universal: true +# universal: false ---index-url https://pypi.org/simple/ ---extra-index-url https://download.pytorch.org/whl/cpu - --e file:. --e file:api - # via quivr-worker --e file:core - # via quivr-api - # via quivr-worker --e file:worker --e file:worker/diff-assistant - # via quivr-worker aiofiles==23.2.1 # via chainlit # via quivr-core -aiohappyeyeballs==2.4.0 +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.5 +aiohttp==3.10.10 # via langchain # via langchain-community - # via litellm # via llama-index-core # via llama-index-legacy - # via realtime aiosignal==1.3.1 # via aiohttp -amqp==5.2.0 - # via kombu +alembic==1.13.3 + # via mlflow +aniso8601==9.0.1 + # via graphene annotated-types==0.7.0 # via pydantic -anthropic==0.34.1 +anthropic==0.36.1 # via langchain-anthropic antlr4-python3-runtime==4.9.3 # via omegaconf -anyascii==0.3.2 - # via python-doctr -anyio==3.7.1 +anyio==4.6.2.post1 # via anthropic # via asyncer # via httpx # via openai # via starlette # via watchfiles -appnope==0.1.4 ; platform_system == 'Darwin' - # via ipykernel -asttokens==2.4.1 - # via stack-data -async-timeout==4.0.3 ; python_full_version < '3.12' - # via asyncpg -asyncer==0.0.2 +asyncer==0.0.7 # via chainlit -asyncpg==0.29.0 - # via quivr-api -attrs==24.2.0 +attrs==23.2.0 # via aiohttp # via jsonschema # via referencing -babel==2.16.0 - # via mkdocs-material + # via sagemaker backoff==2.2.1 - # via posthog # via unstructured beautifulsoup4==4.12.3 # via llama-index-readers-file - # via markdownify - # via nbconvert # via unstructured bidict==0.23.1 # via python-socketio -billiard==4.2.0 - # via celery -black==24.8.0 - # via flake8-black -bleach==6.1.0 - # via nbconvert -boto3==1.35.2 +blinker==1.8.2 + # via flask +boto3==1.35.42 # via cohere -botocore==1.35.2 + # via sagemaker + # via sagemaker-core + # via sagemaker-mlflow +botocore==1.35.42 # via boto3 # via s3transfer cachetools==5.5.0 # via google-auth - # via tox -celery==5.4.0 - # via flower - # via quivr-api - # via quivr-worker -certifi==2022.12.7 + # via mlflow-skinny +certifi==2024.8.30 # via httpcore # via httpx # via requests - # via sentry-sdk - # via unstructured-client -cffi==1.17.0 ; platform_python_implementation != 'PyPy' or implementation_name == 'pypy' +cffi==1.17.1 # via cryptography - # via pyzmq -cfgv==3.4.0 - # via pre-commit -chainlit==1.1.402 +chainlit==1.2.0 chardet==5.2.0 - # via tox # via unstructured -charset-normalizer==2.1.1 +charset-normalizer==3.4.0 # via pdfminer-six # via requests - # via unstructured-client chevron==0.14.0 # via literalai click==8.1.7 - # via black - # via celery # via chainlit - # via click-didyoumean - # via click-plugins - # via click-repl - # via litellm - # via mkdocs - # via mkdocstrings + # via flask + # via llama-parse + # via mlflow-skinny # via nltk # via python-oxmsg # via uvicorn -click-didyoumean==0.3.1 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.3.0 - # via celery +cloudpickle==2.2.1 + # via mlflow-skinny + # via sagemaker cobble==0.1.4 # via mammoth -cohere==5.8.1 +cohere==5.11.0 # via langchain-cohere -colorama==0.4.6 - # via click - # via colorlog - # via griffe - # via ipython - # via mkdocs - # via mkdocs-material - # via pytest - # via tox - # via tqdm coloredlogs==15.0.1 # via onnxruntime -colorlog==6.8.2 - # via quivr-api -comm==0.2.2 - # via ipykernel -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib -coverage==7.6.1 - # via pytest-cov -cryptography==43.0.0 - # via msal +cryptography==43.0.1 # via pdfminer-six - # via pyjwt + # via unstructured-client cycler==0.12.1 # via matplotlib -dataclasses-json==0.5.14 +databricks-sdk==0.34.0 + # via mlflow-skinny +dataclasses-json==0.6.7 # via chainlit # via langchain-community # via llama-index-core # via llama-index-legacy # via unstructured - # via unstructured-client -debugpy==1.8.5 - # via ipykernel -decorator==5.1.1 - # via ipython defusedxml==0.7.1 - # via fpdf2 # via langchain-anthropic - # via nbconvert - # via python-doctr deprecated==1.2.14 # via llama-index-core # via llama-index-legacy @@ -190,192 +121,143 @@ deprecated==1.2.14 # via opentelemetry-exporter-otlp-proto-http # via opentelemetry-semantic-conventions # via pikepdf -deprecation==2.1.0 - # via postgrest -diff-match-patch==20230430 - # via quivr-diff-assistant +dill==0.3.9 + # via multiprocess + # via pathos dirtyjson==1.0.8 # via llama-index-core # via llama-index-legacy -distlib==0.3.8 - # via virtualenv distro==1.9.0 # via anthropic # via openai +docker==7.1.0 + # via mlflow + # via sagemaker docx2txt==0.8 # via quivr-core - # via quivr-diff-assistant -dropbox==12.0.2 - # via quivr-api -ecdsa==0.19.0 - # via python-jose effdet==0.4.1 # via unstructured -emoji==2.12.1 +emoji==2.14.0 # via unstructured et-xmlfile==1.1.0 # via openpyxl -execnet==2.1.1 - # via pytest-xdist -executing==2.0.1 - # via stack-data -faiss-cpu==1.8.0.post1 +eval-type-backport==0.2.0 + # via unstructured-client +faiss-cpu==1.9.0 # via quivr-core - # via quivr-diff-assistant -fastapi==0.110.3 +fastapi==0.112.4 # via chainlit - # via quivr-api - # via sentry-sdk -fastavro==1.9.5 +fastavro==1.9.7 # via cohere -fastjsonschema==2.20.0 - # via nbformat -filelock==3.15.4 +filelock==3.16.1 # via huggingface-hub # via torch - # via tox # via transformers - # via virtualenv + # via triton filetype==1.2.0 # via chainlit # via unstructured -fire==0.6.0 +fire==0.7.0 # via pdf2docx -flake8==7.1.1 - # via flake8-black -flake8-black==0.3.6 +flask==3.0.3 + # via mlflow flatbuffers==24.3.25 # via onnxruntime -flower==2.0.1 - # via quivr-worker -fonttools==4.53.1 - # via fpdf2 +fonttools==4.54.1 # via matplotlib # via pdf2docx -fpdf2==2.7.9 - # via quivr-worker frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.2.0 +fsspec==2024.9.0 # via huggingface-hub # via llama-index-core # via llama-index-legacy # via torch -ghp-import==2.1.0 - # via mkdocs -google-api-core==2.19.1 - # via google-api-python-client +gitdb==4.0.11 + # via gitpython +gitpython==3.1.43 + # via mlflow-skinny +google-api-core==2.21.0 # via google-cloud-vision -google-api-python-client==2.142.0 - # via quivr-api -google-auth==2.34.0 +google-auth==2.35.0 + # via databricks-sdk # via google-api-core - # via google-api-python-client - # via google-auth-httplib2 - # via google-auth-oauthlib # via google-cloud-vision -google-auth-httplib2==0.2.0 - # via google-api-python-client - # via quivr-api -google-auth-oauthlib==1.2.1 - # via quivr-api google-cloud-vision==3.7.4 # via unstructured -googleapis-common-protos==1.63.2 +google-pasta==0.2.0 + # via sagemaker +googleapis-common-protos==1.65.0 # via google-api-core # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via opentelemetry-exporter-otlp-proto-http -gotrue==2.7.0 - # via supabase -greenlet==3.0.3 - # via playwright +graphene==3.3 + # via mlflow +graphql-core==3.2.5 + # via graphene + # via graphql-relay +graphql-relay==3.2.0 + # via graphene +greenlet==3.1.1 # via sqlalchemy -griffe==1.2.0 - # via mkdocstrings-python -grpcio==1.65.5 +grpcio==1.67.0 # via google-api-core # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc grpcio-status==1.62.3 # via google-api-core +gunicorn==23.0.0 + # via mlflow h11==0.14.0 # via httpcore # via uvicorn # via wsproto -h2==4.1.0 +httpcore==1.0.6 # via httpx -h5py==3.10.0 - # via python-doctr - # via quivr-diff-assistant -hpack==4.0.0 - # via h2 -httpcore==1.0.5 - # via httpx -httplib2==0.22.0 - # via google-api-python-client - # via google-auth-httplib2 -httpx==0.27.0 +httpx==0.27.2 # via anthropic # via chainlit # via cohere - # via gotrue + # via langgraph-sdk # via langsmith # via literalai # via llama-cloud # via llama-index-core # via llama-index-legacy - # via notion-client # via openai - # via postgrest # via quivr-core - # via storage3 - # via supabase - # via supafunc + # via unstructured-client httpx-sse==0.4.0 # via cohere -huggingface-hub==0.24.6 - # via python-doctr + # via langgraph-sdk +huggingface-hub==0.25.2 # via timm # via tokenizers # via transformers # via unstructured-inference humanfriendly==10.0 # via coloredlogs -humanize==4.10.0 - # via flower -hyperframe==6.0.1 - # via h2 -identify==2.6.0 - # via pre-commit -idna==3.4 +idna==3.10 # via anyio # via httpx # via requests - # via unstructured-client # via yarl -importlib-metadata==8.4.0 - # via litellm +importlib-metadata==6.11.0 + # via mlflow-skinny # via opentelemetry-api -iniconfig==2.0.0 - # via pytest + # via sagemaker + # via sagemaker-core iopath==0.1.10 # via layoutparser -ipykernel==6.29.5 - # via mkdocs-jupyter -ipython==8.26.0 - # via ipykernel -jedi==0.19.1 - # via ipython -jinja2==3.1.3 - # via litellm - # via mkdocs - # via mkdocs-material - # via mkdocstrings - # via nbconvert +itsdangerous==2.2.0 + # via flask +jinja2==3.1.4 + # via flask + # via mlflow # via torch -jiter==0.5.0 +jiter==0.6.1 # via anthropic # via openai jmespath==1.0.1 @@ -391,41 +273,22 @@ jsonpath-python==1.0.6 jsonpointer==3.0.0 # via jsonpatch jsonschema==4.23.0 - # via litellm - # via nbformat -jsonschema-specifications==2023.12.1 + # via sagemaker + # via sagemaker-core +jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-client==8.6.2 - # via ipykernel - # via nbclient -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client - # via nbclient - # via nbconvert - # via nbformat -jupyterlab-pygments==0.3.0 - # via nbconvert -jupytext==1.16.4 - # via mkdocs-jupyter -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib -kombu==5.4.0 - # via celery langchain==0.2.16 # via langchain-community # via megaparse - # via quivr-api # via quivr-core - # via quivr-diff-assistant langchain-anthropic==0.1.23 # via quivr-core -langchain-cohere==0.2.2 - # via quivr-api -langchain-community==0.2.12 +langchain-cohere==0.2.4 +langchain-community==0.2.17 # via langchain-experimental # via megaparse - # via quivr-api # via quivr-core langchain-core==0.2.41 # via langchain @@ -439,23 +302,21 @@ langchain-core==0.2.41 # via langgraph-checkpoint # via megaparse # via quivr-core -langchain-experimental==0.0.64 +langchain-experimental==0.0.65 # via langchain-cohere langchain-openai==0.1.25 # via megaparse - # via quivr-api - # via quivr-core - # via quivr-diff-assistant -langchain-text-splitters==0.2.2 +langchain-text-splitters==0.2.4 # via langchain langdetect==1.0.9 - # via python-doctr # via unstructured -langgraph==0.2.14 +langgraph==0.2.38 # via quivr-core -langgraph-checkpoint==1.0.6 +langgraph-checkpoint==2.0.1 # via langgraph -langsmith==0.1.126 +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 # via langchain # via langchain-community # via langchain-core @@ -463,22 +324,18 @@ layoutparser==0.3.4 # via unstructured-inference lazify==0.4.0 # via chainlit -litellm==1.43.19 - # via quivr-api literalai==0.0.607 # via chainlit -llama-cloud==0.0.13 +llama-cloud==0.1.2 # via llama-index-indices-managed-llama-cloud -llama-index==0.11.12 +llama-index==0.11.18 # via megaparse - # via quivr-core - # via quivr-diff-assistant llama-index-agent-openai==0.3.4 # via llama-index # via llama-index-program-openai llama-index-cli==0.3.1 # via llama-index -llama-index-core==0.11.12 +llama-index-core==0.11.18 # via llama-index # via llama-index-agent-openai # via llama-index-cli @@ -494,19 +351,18 @@ llama-index-core==0.11.12 llama-index-embeddings-openai==0.2.5 # via llama-index # via llama-index-cli -llama-index-indices-managed-llama-cloud==0.3.1 +llama-index-indices-managed-llama-cloud==0.4.0 # via llama-index llama-index-legacy==0.9.48.post3 # via llama-index -llama-index-llms-openai==0.2.9 +llama-index-llms-openai==0.2.15 # via llama-index # via llama-index-agent-openai # via llama-index-cli # via llama-index-multi-modal-llms-openai # via llama-index-program-openai # via llama-index-question-gen-openai - # via quivr-diff-assistant -llama-index-multi-modal-llms-openai==0.2.1 +llama-index-multi-modal-llms-openai==0.2.2 # via llama-index llama-index-program-openai==0.2.0 # via llama-index @@ -515,125 +371,62 @@ llama-index-question-gen-openai==0.2.0 # via llama-index llama-index-readers-file==0.2.2 # via llama-index - # via quivr-diff-assistant llama-index-readers-llama-parse==0.3.0 # via llama-index -llama-parse==0.5.6 +llama-parse==0.5.9 # via llama-index-readers-llama-parse # via megaparse - # via quivr-api - # via quivr-core -llvmlite==0.43.0 - # via numba lxml==5.3.0 # via pikepdf # via python-docx # via python-pptx # via unstructured +mako==1.3.5 + # via alembic mammoth==1.8.0 # via megaparse - # via quivr-core markdown==3.7 - # via mkdocs - # via mkdocs-autorefs - # via mkdocs-material - # via mkdocstrings - # via pymdown-extensions + # via mlflow # via unstructured markdown-it-py==3.0.0 - # via jupytext - # via mdit-py-plugins # via rich -markdownify==0.13.1 - # via quivr-api -markupsafe==2.1.5 +markupsafe==3.0.1 # via jinja2 - # via mkdocs - # via mkdocs-autorefs - # via mkdocstrings - # via nbconvert + # via mako + # via werkzeug marshmallow==3.22.0 # via dataclasses-json - # via marshmallow-enum - # via unstructured-client -marshmallow-enum==1.5.1 - # via unstructured-client matplotlib==3.9.2 - # via mplcursors + # via mlflow # via pycocotools - # via quivr-diff-assistant # via unstructured-inference -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython -mccabe==0.7.0 - # via flake8 -mdit-py-plugins==0.4.1 - # via jupytext mdurl==0.1.2 # via markdown-it-py megaparse==0.0.31 # via quivr-core - # via quivr-diff-assistant -mergedeep==1.3.4 - # via mkdocs - # via mkdocs-get-deps -mistune==3.0.2 - # via nbconvert -mkdocs==1.6.1 - # via mkdocs-autorefs - # via mkdocs-include-dir-to-nav - # via mkdocs-jupyter - # via mkdocs-material - # via mkdocs-redirects - # via mkdocstrings -mkdocs-autorefs==1.2.0 - # via mkdocstrings - # via mkdocstrings-python -mkdocs-get-deps==0.2.0 - # via mkdocs -mkdocs-include-dir-to-nav==1.2.0 -mkdocs-jupyter==0.25.0 -mkdocs-material==9.5.34 - # via mkdocs-jupyter -mkdocs-material-extensions==1.3.1 - # via mkdocs-material -mkdocs-redirects==1.2.1 -mkdocstrings==0.26.1 - # via mkdocstrings-python -mkdocstrings-python==1.11.1 - # via mkdocstrings -monotonic==1.6 - # via posthog -mplcursors==0.5.3 - # via quivr-diff-assistant +mlflow==2.17.0 + # via sagemaker-mlflow +mlflow-skinny==2.17.0 + # via mlflow +mock==4.0.3 + # via sagemaker-core mpmath==1.3.0 # via sympy -msal==1.30.0 - # via quivr-api -multidict==6.0.5 +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 # via aiohttp # via yarl -mypy==1.11.1 +multiprocess==0.70.17 + # via pathos mypy-extensions==1.0.0 - # via black - # via mypy # via typing-inspect - # via unstructured-client -nbclient==0.10.0 - # via nbconvert -nbconvert==7.16.4 - # via mkdocs-jupyter -nbformat==5.10.4 - # via jupytext - # via nbclient - # via nbconvert nest-asyncio==1.6.0 # via chainlit - # via ipykernel # via llama-index-core # via llama-index-legacy -networkx==3.2.1 + # via unstructured-client +networkx==3.4.1 # via llama-index-core # via llama-index-legacy # via torch @@ -643,72 +436,83 @@ nltk==3.9.1 # via llama-index-core # via llama-index-legacy # via unstructured -nodeenv==1.9.1 - # via pre-commit -notion-client==2.2.1 - # via quivr-api -numba==0.60.0 - # via quivr-diff-assistant -numpy==1.26.3 +numpy==1.26.4 # via chainlit # via contourpy # via faiss-cpu - # via h5py # via langchain # via langchain-community # via layoutparser # via llama-index-core # via llama-index-legacy # via matplotlib - # via numba + # via mlflow # via onnx # via onnxruntime # via opencv-python # via opencv-python-headless # via pandas # via pdf2docx - # via pgvector + # via pyarrow # via pycocotools - # via python-doctr - # via quivr-diff-assistant + # via sagemaker # via scikit-learn # via scipy - # via shapely # via torchvision # via transformers # via unstructured -oauthlib==3.2.2 - # via requests-oauthlib +nvidia-cublas-cu12==12.1.3.1 + # via nvidia-cudnn-cu12 + # via nvidia-cusolver-cu12 + # via torch +nvidia-cuda-cupti-cu12==12.1.105 + # via torch +nvidia-cuda-nvrtc-cu12==12.1.105 + # via torch +nvidia-cuda-runtime-cu12==12.1.105 + # via torch +nvidia-cudnn-cu12==9.1.0.70 + # via torch +nvidia-cufft-cu12==11.0.2.54 + # via torch +nvidia-curand-cu12==10.3.2.106 + # via torch +nvidia-cusolver-cu12==11.4.5.107 + # via torch +nvidia-cusparse-cu12==12.1.0.106 + # via nvidia-cusolver-cu12 + # via torch +nvidia-nccl-cu12==2.20.5 + # via torch +nvidia-nvjitlink-cu12==12.6.77 + # via nvidia-cusolver-cu12 + # via nvidia-cusparse-cu12 +nvidia-nvtx-cu12==12.1.105 + # via torch olefile==0.47 # via python-oxmsg omegaconf==2.3.0 # via effdet -onnx==1.16.2 +onnx==1.17.0 # via unstructured # via unstructured-inference -onnxruntime==1.19.0 +onnxruntime==1.19.2 # via unstructured-inference -openai==1.47.1 +openai==1.51.2 # via langchain-openai - # via litellm # via llama-index-agent-openai # via llama-index-embeddings-openai # via llama-index-legacy # via llama-index-llms-openai - # via quivr-api - # via quivr-diff-assistant - # via quivr-worker opencv-python==4.10.0.84 # via layoutparser - # via python-doctr - # via quivr-diff-assistant # via unstructured-inference opencv-python-headless==4.10.0.84 # via pdf2docx openpyxl==3.1.5 - # via quivr-diff-assistant # via unstructured opentelemetry-api==1.27.0 + # via mlflow-skinny # via opentelemetry-exporter-otlp-proto-grpc # via opentelemetry-exporter-otlp-proto-http # via opentelemetry-instrumentation @@ -731,58 +535,44 @@ opentelemetry-proto==1.27.0 # via opentelemetry-exporter-otlp-proto-grpc # via opentelemetry-exporter-otlp-proto-http opentelemetry-sdk==1.27.0 + # via mlflow-skinny # via opentelemetry-exporter-otlp-proto-grpc # via opentelemetry-exporter-otlp-proto-http # via uptrace opentelemetry-semantic-conventions==0.48b0 # via opentelemetry-sdk orjson==3.10.7 + # via langgraph-sdk # via langsmith - # via quivr-monorepo packaging==23.2 - # via black # via chainlit - # via deprecation # via faiss-cpu + # via gunicorn # via huggingface-hub - # via ipykernel - # via jupytext # via langchain-core # via literalai # via marshmallow # via matplotlib - # via mkdocs - # via nbconvert + # via mlflow-skinny # via onnxruntime # via pikepdf - # via pyproject-api - # via pytest - # via quivr-monorepo - # via tox + # via sagemaker # via transformers - # via unstructured-client # via unstructured-pytesseract -paginate==0.5.7 - # via mkdocs-material -pandas==2.2.2 +pandas==2.2.3 # via langchain-cohere # via layoutparser # via llama-index-legacy # via llama-index-readers-file - # via quivr-diff-assistant + # via mlflow + # via sagemaker # via unstructured -pandocfilters==1.5.1 - # via nbconvert parameterized==0.9.0 # via cohere -parso==0.8.4 - # via jedi -pathspec==0.12.1 - # via black - # via mkdocs +pathos==0.3.3 + # via sagemaker pdf2docx==0.5.8 # via megaparse - # via quivr-core pdf2image==1.17.0 # via layoutparser # via unstructured @@ -792,17 +582,11 @@ pdfminer-six==20231228 pdfplumber==0.11.4 # via layoutparser # via megaparse - # via quivr-core -pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via ipython -pgvector==0.3.2 - # via quivr-api pi-heif==0.18.0 # via unstructured -pikepdf==9.1.1 +pikepdf==9.3.0 # via unstructured -pillow==10.2.0 - # via fpdf2 +pillow==11.0.0 # via layoutparser # via llama-index-core # via matplotlib @@ -810,315 +594,202 @@ pillow==10.2.0 # via pdfplumber # via pi-heif # via pikepdf - # via python-doctr # via python-pptx # via torchvision # via unstructured-pytesseract -platformdirs==4.2.2 - # via black - # via jupyter-core - # via mkdocs-get-deps - # via mkdocstrings - # via tox - # via virtualenv -playwright==1.46.0 - # via quivr-worker -pluggy==1.5.0 - # via pytest - # via tox -ply==3.11 - # via stone +platformdirs==4.3.6 + # via sagemaker + # via sagemaker-core portalocker==2.10.1 # via iopath -postgrest==0.16.10 - # via supabase -posthog==3.5.0 - # via quivr-api -pre-commit==3.8.0 -prometheus-client==0.20.0 - # via flower -prompt-toolkit==3.0.47 - # via click-repl - # via ipython +pox==0.3.5 + # via pathos +ppft==1.7.6.9 + # via pathos +propcache==0.2.0 + # via yarl proto-plus==1.24.0 # via google-api-core # via google-cloud-vision -protobuf==4.25.4 +protobuf==4.25.5 # via google-api-core # via google-cloud-vision # via googleapis-common-protos # via grpcio-status + # via mlflow-skinny # via onnx # via onnxruntime # via opentelemetry-proto # via proto-plus + # via sagemaker # via transformers psutil==6.0.0 - # via ipykernel + # via sagemaker # via unstructured -psycopg2-binary==2.9.9 - # via quivr-api -ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via pexpect -pure-eval==0.2.3 - # via stack-data -py-cpuinfo==9.0.0 - # via pytest-benchmark -pyasn1==0.6.0 +pyarrow==17.0.0 + # via mlflow +pyasn1==0.6.1 # via pyasn1-modules - # via python-jose # via rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via google-auth -pyclipper==1.3.0.post5 - # via python-doctr pycocotools==2.0.8 # via effdet -pycodestyle==2.12.1 - # via flake8 -pycparser==2.22 ; platform_python_implementation != 'PyPy' or implementation_name == 'pypy' +pycparser==2.22 # via cffi -pycryptodome==3.20.0 +pycryptodome==3.21.0 # via megaparse - # via quivr-core -pydantic==2.8.2 +pydantic==2.9.2 # via anthropic # via chainlit # via cohere # via fastapi - # via gotrue # via langchain # via langchain-core # via langsmith - # via litellm # via literalai # via llama-cloud # via llama-index-core # via openai - # via postgrest - # via pydantic-settings # via quivr-core + # via sagemaker-core # via sqlmodel -pydantic-core==2.20.1 + # via unstructured-client +pydantic-core==2.23.4 # via cohere # via pydantic -pydantic-settings==2.4.0 - # via quivr-api -pyee==11.1.0 - # via playwright -pyflakes==3.2.0 - # via flake8 pygments==2.18.0 - # via ipython - # via mkdocs-jupyter - # via mkdocs-material - # via nbconvert # via rich -pyinstrument==4.7.2 - # via quivr-api pyjwt==2.9.0 # via chainlit - # via msal -pymdown-extensions==10.9 - # via mkdocs-material - # via mkdocstrings -pymupdf==1.24.9 +pymupdf==1.24.11 # via pdf2docx -pymupdfb==1.24.9 - # via pymupdf -pypandoc==1.13 +pypandoc==1.14 # via unstructured -pyparsing==3.1.2 - # via httplib2 +pyparsing==3.2.0 # via matplotlib - # via unstructured-client pypdf==4.3.1 # via llama-index-readers-file - # via quivr-diff-assistant # via unstructured + # via unstructured-client pypdfium2==4.30.0 # via pdfplumber - # via python-doctr - # via quivr-diff-assistant -pyproject-api==1.6.1 - # via tox -pyreadline3==3.4.1 ; sys_platform == 'win32' - # via humanfriendly -pytest==8.3.2 - # via pytest-asyncio - # via pytest-benchmark - # via pytest-cov - # via pytest-dotenv - # via pytest-mock - # via pytest-xdist -pytest-asyncio==0.24.0 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-dotenv==0.5.2 -pytest-mock==3.14.0 -pytest-xdist==3.6.1 -python-dateutil==2.9.0.post0 +python-dateutil==2.8.2 # via botocore - # via celery - # via ghp-import - # via jupyter-client # via matplotlib # via pandas - # via posthog - # via realtime - # via storage3 # via unstructured-client -python-doctr==0.9.0 - # via quivr-diff-assistant python-docx==1.1.2 # via megaparse # via pdf2docx - # via quivr-core # via unstructured python-dotenv==1.0.1 # via chainlit - # via litellm # via megaparse - # via pydantic-settings - # via pytest-dotenv - # via quivr-api - # via quivr-core - # via quivr-diff-assistant - # via quivr-worker -python-engineio==4.9.1 +python-engineio==4.10.1 # via python-socketio python-iso639==2024.4.27 # via unstructured -python-jose==3.3.0 - # via quivr-api -python-json-logger==2.0.7 - # via quivr-monorepo python-magic==0.4.27 - # via quivr-diff-assistant # via unstructured python-multipart==0.0.9 # via chainlit - # via quivr-api # via unstructured-inference python-oxmsg==0.0.1 # via unstructured python-pptx==1.0.2 # via megaparse - # via quivr-core # via unstructured -python-socketio==5.11.3 +python-socketio==5.11.4 # via chainlit -pytz==2024.1 - # via flower +pytz==2024.2 # via pandas -pywin32==306 ; (platform_python_implementation != 'PyPy' and sys_platform == 'win32') or platform_system == 'Windows' - # via jupyter-core - # via portalocker pyyaml==6.0.2 # via huggingface-hub - # via jupytext # via langchain # via langchain-community # via langchain-core # via layoutparser # via llama-index-core - # via mkdocs - # via mkdocs-get-deps + # via mlflow-skinny # via omegaconf - # via pre-commit - # via pymdown-extensions - # via pyyaml-env-tag + # via sagemaker + # via sagemaker-core # via timm # via transformers -pyyaml-env-tag==0.1 - # via mkdocs -pyzmq==26.1.1 - # via ipykernel - # via jupyter-client -rapidfuzz==3.9.6 - # via python-doctr +quivr-core==0.0.18 +rapidfuzz==3.10.0 # via unstructured # via unstructured-inference -realtime==2.0.2 - # via supabase -redis==5.0.8 - # via celery - # via quivr-api referencing==0.35.1 # via jsonschema # via jsonschema-specifications -regex==2024.7.24 - # via mkdocs-material +regex==2024.9.11 # via nltk # via tiktoken # via transformers requests==2.32.3 # via cohere - # via dropbox + # via databricks-sdk + # via docker # via google-api-core # via huggingface-hub # via langchain # via langchain-community # via langsmith - # via litellm # via llama-index-core # via llama-index-legacy - # via mkdocs-material - # via msal + # via mlflow-skinny # via opentelemetry-exporter-otlp-proto-http - # via posthog - # via requests-oauthlib - # via resend + # via requests-toolbelt + # via sagemaker # via tiktoken # via transformers # via unstructured +requests-toolbelt==1.0.0 + # via langsmith # via unstructured-client -requests-oauthlib==2.0.0 - # via google-auth-oauthlib -resend==2.4.0 - # via quivr-api -rich==13.7.1 +rich==13.9.2 # via quivr-core + # via sagemaker-core rpds-py==0.20.0 # via jsonschema # via referencing rsa==4.9 # via google-auth - # via python-jose -ruff==0.6.1 -s3transfer==0.10.2 +s3transfer==0.10.3 # via boto3 -safetensors==0.4.4 +safetensors==0.4.5 # via timm # via transformers +sagemaker==2.232.2 + # via cohere +sagemaker-core==1.0.10 + # via sagemaker +sagemaker-mlflow==0.1.0 + # via sagemaker +schema==0.7.7 + # via sagemaker scikit-learn==1.5.2 - # via quivr-diff-assistant + # via mlflow scipy==1.14.1 # via layoutparser - # via python-doctr + # via mlflow # via scikit-learn sentencepiece==0.2.0 # via transformers -sentry-sdk==2.13.0 - # via quivr-api -setuptools==70.0.0 +setuptools==75.2.0 # via opentelemetry-instrumentation -shapely==2.0.6 - # via python-doctr -simple-websocket==1.0.0 +simple-websocket==1.1.0 # via python-engineio six==1.16.0 - # via asttokens - # via bleach - # via dropbox - # via ecdsa - # via fire + # via google-pasta # via langdetect - # via markdownify - # via posthog # via python-dateutil - # via stone - # via unstructured-client +smdebug-rulesconfig==1.0.1 + # via sagemaker +smmap==5.0.1 + # via gitdb sniffio==1.3.1 # via anthropic # via anyio @@ -1126,34 +797,23 @@ sniffio==1.3.1 # via openai soupsieve==2.6 # via beautifulsoup4 -sqlalchemy==2.0.32 +sqlalchemy==2.0.36 + # via alembic # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy + # via mlflow # via sqlmodel -sqlmodel==0.0.21 - # via quivr-api -stack-data==0.6.3 - # via ipython +sqlmodel==0.0.22 +sqlparse==0.5.1 + # via mlflow-skinny starlette==0.37.2 # via chainlit # via fastapi -stone==3.3.1 - # via dropbox -storage3==0.7.7 - # via supabase -strenum==0.4.15 - # via postgrest striprtf==0.0.26 # via llama-index-readers-file -structlog==24.4.0 - # via quivr-monorepo -supabase==2.7.2 - # via quivr-api -supafunc==0.5.1 - # via supabase -sympy==1.12 +sympy==1.13.3 # via onnxruntime # via torch syncer==2.0.3 @@ -1161,168 +821,109 @@ syncer==2.0.3 tabulate==0.9.0 # via langchain-cohere # via unstructured +tblib==3.0.0 + # via sagemaker tenacity==8.5.0 # via langchain # via langchain-community # via langchain-core # via llama-index-core # via llama-index-legacy -termcolor==2.4.0 +termcolor==2.5.0 # via fire threadpoolctl==3.5.0 # via scikit-learn -tiktoken==0.7.0 +tiktoken==0.8.0 # via langchain-openai - # via litellm # via llama-index-core # via llama-index-legacy - # via quivr-api # via quivr-core -timm==1.0.8 +timm==1.0.11 # via effdet # via unstructured-inference -tinycss2==1.3.0 - # via nbconvert -tokenizers==0.19.1 +tokenizers==0.20.1 # via anthropic # via cohere - # via litellm # via transformers -tomli==2.0.1 +tomli==2.0.2 # via chainlit -torch==2.4.0 ; platform_machine != 'x86_64' +torch==2.4.1 # via effdet - # via quivr-worker # via timm # via torchvision # via unstructured-inference -torch==2.4.0+cpu ; platform_machine == 'x86_64' +torchvision==0.19.1 # via effdet - # via quivr-worker # via timm - # via torchvision - # via unstructured-inference -torchvision==0.19.0 ; platform_machine != 'x86_64' - # via effdet - # via quivr-worker - # via timm -torchvision==0.19.0+cpu ; platform_machine == 'x86_64' - # via effdet - # via quivr-worker - # via timm -tornado==6.4.1 - # via flower - # via ipykernel - # via jupyter-client -tox==4.15.1 tqdm==4.66.5 # via huggingface-hub # via iopath # via llama-index-core # via nltk # via openai - # via python-doctr + # via sagemaker # via transformers # via unstructured -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline - # via nbclient - # via nbconvert - # via nbformat -transformers==4.44.2 +transformers==4.45.2 # via quivr-core # via unstructured-inference -types-pyyaml==6.0.12.20240808 +triton==3.0.0 + # via torch +types-pyyaml==6.0.12.20240917 # via quivr-core -types-requests==2.31.0.6 +types-requests==2.32.0.20241016 # via cohere -types-urllib3==1.26.25.14 - # via types-requests typing-extensions==4.12.2 + # via alembic # via anthropic # via cohere - # via emoji # via fastapi # via huggingface-hub # via iopath - # via ipython # via langchain-core # via llama-index-core # via llama-index-legacy - # via mypy # via openai # via opentelemetry-sdk # via pydantic # via pydantic-core - # via pyee # via python-docx # via python-oxmsg # via python-pptx - # via realtime - # via resend # via sqlalchemy - # via storage3 # via torch # via typing-inspect # via unstructured - # via unstructured-client typing-inspect==0.9.0 # via dataclasses-json # via llama-index-core # via llama-index-legacy # via unstructured-client -tzdata==2024.1 - # via celery +tzdata==2024.2 # via pandas -unidecode==1.3.8 - # via quivr-api -unstructured==0.15.13 +unstructured==0.15.14 # via megaparse # via quivr-core - # via quivr-diff-assistant -unstructured-client==0.6.0 +unstructured-client==0.26.1 # via unstructured unstructured-inference==0.7.36 # via unstructured unstructured-pytesseract==0.3.13 # via unstructured -uptrace==1.26.0 +uptrace==1.27.0 # via chainlit -uritemplate==4.1.1 - # via google-api-python-client -urllib3==1.26.13 +urllib3==2.2.3 # via botocore + # via docker # via requests - # via sentry-sdk - # via unstructured-client + # via sagemaker + # via types-requests uvicorn==0.25.0 # via chainlit - # via quivr-api -uvloop==0.20.0 - # via quivr-api -vine==5.1.0 - # via amqp - # via celery - # via kombu -virtualenv==20.26.3 - # via pre-commit - # via tox -watchdog==5.0.2 - # via mkdocs watchfiles==0.20.0 # via chainlit -wcwidth==0.2.13 - # via prompt-toolkit -webencodings==0.5.1 - # via bleach - # via tinycss2 -websockets==12.0 - # via realtime +werkzeug==3.0.4 + # via flask wrapt==1.16.0 # via deprecated # via llama-index-core @@ -1334,7 +935,7 @@ xlrd==2.0.1 # via unstructured xlsxwriter==3.2.0 # via python-pptx -yarl==1.9.4 +yarl==1.15.4 # via aiohttp -zipp==3.20.0 +zipp==3.20.2 # via importlib-metadata diff --git a/backend/requirements.lock b/examples/chatbot/requirements.lock similarity index 50% rename from backend/requirements.lock rename to examples/chatbot/requirements.lock index 9ce248580..8a856b6cc 100644 --- a/backend/requirements.lock +++ b/examples/chatbot/requirements.lock @@ -3,330 +3,261 @@ # # last locked with the following flags: # pre: false -# features: ["quivr-core/all"] +# features: [] # all-features: false -# with-sources: true +# with-sources: false # generate-hashes: false -# universal: true +# universal: false ---index-url https://pypi.org/simple/ ---extra-index-url https://download.pytorch.org/whl/cpu - --e file:. --e file:api - # via quivr-worker --e file:core - # via quivr-api - # via quivr-worker --e file:worker --e file:worker/diff-assistant - # via quivr-worker -aiofiles==24.1.0 +aiofiles==23.2.1 + # via chainlit # via quivr-core -aiohappyeyeballs==2.4.0 +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.5 +aiohttp==3.10.10 # via langchain # via langchain-community - # via litellm # via llama-index-core # via llama-index-legacy - # via realtime aiosignal==1.3.1 # via aiohttp -amqp==5.2.0 - # via kombu +alembic==1.13.3 + # via mlflow +aniso8601==9.0.1 + # via graphene annotated-types==0.7.0 # via pydantic -anthropic==0.34.2 +anthropic==0.36.1 # via langchain-anthropic antlr4-python3-runtime==4.9.3 # via omegaconf -anyascii==0.3.2 - # via python-doctr -anyio==4.4.0 +anyio==4.6.2.post1 # via anthropic + # via asyncer # via httpx # via openai # via starlette -appnope==0.1.4 ; platform_system == 'Darwin' - # via ipykernel -asttokens==2.4.1 - # via stack-data -async-timeout==4.0.3 ; python_full_version < '3.12' - # via asyncpg -asyncpg==0.29.0 - # via quivr-api -attrs==24.2.0 + # via watchfiles +asyncer==0.0.7 + # via chainlit +attrs==23.2.0 # via aiohttp # via jsonschema # via referencing -babel==2.16.0 - # via mkdocs-material + # via sagemaker backoff==2.2.1 - # via posthog # via unstructured beautifulsoup4==4.12.3 # via llama-index-readers-file - # via markdownify - # via nbconvert # via unstructured -billiard==4.2.0 - # via celery -bleach==6.1.0 - # via nbconvert -boto3==1.35.2 +bidict==0.23.1 + # via python-socketio +blinker==1.8.2 + # via flask +boto3==1.35.42 # via cohere -botocore==1.35.2 + # via sagemaker + # via sagemaker-core + # via sagemaker-mlflow +botocore==1.35.42 # via boto3 # via s3transfer cachetools==5.5.0 # via google-auth -celery==5.4.0 - # via flower - # via quivr-api - # via quivr-worker -certifi==2022.12.7 + # via mlflow-skinny +certifi==2024.8.30 # via httpcore # via httpx # via requests - # via sentry-sdk - # via unstructured-client -cffi==1.17.0 ; platform_python_implementation != 'PyPy' or implementation_name == 'pypy' +cffi==1.17.1 # via cryptography - # via pyzmq +chainlit==1.2.0 chardet==5.2.0 # via unstructured -charset-normalizer==2.1.1 +charset-normalizer==3.4.0 # via pdfminer-six # via requests - # via unstructured-client +chevron==0.14.0 + # via literalai click==8.1.7 - # via celery - # via click-didyoumean - # via click-plugins - # via click-repl - # via litellm - # via mkdocs - # via mkdocstrings + # via chainlit + # via flask + # via llama-parse + # via mlflow-skinny # via nltk # via python-oxmsg # via uvicorn -click-didyoumean==0.3.1 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.3.0 - # via celery +cloudpickle==2.2.1 + # via mlflow-skinny + # via sagemaker cobble==0.1.4 # via mammoth -cohere==5.8.1 +cohere==5.11.0 # via langchain-cohere -colorama==0.4.6 - # via click - # via colorlog - # via griffe - # via ipython - # via mkdocs - # via mkdocs-material - # via tqdm coloredlogs==15.0.1 # via onnxruntime -colorlog==6.8.2 - # via quivr-api -comm==0.2.2 - # via ipykernel -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib -cryptography==43.0.0 - # via msal +cryptography==43.0.1 # via pdfminer-six - # via pyjwt + # via unstructured-client cycler==0.12.1 # via matplotlib +databricks-sdk==0.34.0 + # via mlflow-skinny dataclasses-json==0.6.7 + # via chainlit # via langchain-community # via llama-index-core # via llama-index-legacy # via unstructured - # via unstructured-client -debugpy==1.8.5 - # via ipykernel -decorator==5.1.1 - # via ipython defusedxml==0.7.1 - # via fpdf2 # via langchain-anthropic - # via nbconvert - # via python-doctr deprecated==1.2.14 # via llama-index-core # via llama-index-legacy + # via opentelemetry-api + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http + # via opentelemetry-semantic-conventions # via pikepdf -deprecation==2.1.0 - # via postgrest -diff-match-patch==20230430 - # via quivr-diff-assistant +dill==0.3.9 + # via multiprocess + # via pathos dirtyjson==1.0.8 # via llama-index-core # via llama-index-legacy distro==1.9.0 # via anthropic # via openai +docker==7.1.0 + # via mlflow + # via sagemaker docx2txt==0.8 # via quivr-core - # via quivr-diff-assistant -dropbox==12.0.2 - # via quivr-api -ecdsa==0.19.0 - # via python-jose effdet==0.4.1 # via unstructured -emoji==2.12.1 +emoji==2.14.0 # via unstructured et-xmlfile==1.1.0 # via openpyxl -executing==2.1.0 - # via stack-data -faiss-cpu==1.8.0.post1 +eval-type-backport==0.2.0 + # via unstructured-client +faiss-cpu==1.9.0 # via quivr-core - # via quivr-diff-assistant -fastapi==0.112.1 - # via quivr-api - # via sentry-sdk -fastavro==1.9.5 +fastapi==0.112.4 + # via chainlit +fastavro==1.9.7 # via cohere -fastjsonschema==2.20.0 - # via nbformat -filelock==3.13.1 +filelock==3.16.1 # via huggingface-hub # via torch # via transformers + # via triton filetype==1.2.0 + # via chainlit # via unstructured -fire==0.6.0 +fire==0.7.0 # via pdf2docx +flask==3.0.3 + # via mlflow flatbuffers==24.3.25 # via onnxruntime -flower==2.0.1 - # via quivr-worker -fonttools==4.53.1 - # via fpdf2 +fonttools==4.54.1 # via matplotlib # via pdf2docx -fpdf2==2.7.9 - # via quivr-worker frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.2.0 +fsspec==2024.9.0 # via huggingface-hub # via llama-index-core # via llama-index-legacy # via torch -ghp-import==2.1.0 - # via mkdocs -google-api-core==2.19.1 - # via google-api-python-client +gitdb==4.0.11 + # via gitpython +gitpython==3.1.43 + # via mlflow-skinny +google-api-core==2.21.0 # via google-cloud-vision -google-api-python-client==2.142.0 - # via quivr-api -google-auth==2.34.0 +google-auth==2.35.0 + # via databricks-sdk # via google-api-core - # via google-api-python-client - # via google-auth-httplib2 - # via google-auth-oauthlib # via google-cloud-vision -google-auth-httplib2==0.2.0 - # via google-api-python-client - # via quivr-api -google-auth-oauthlib==1.2.1 - # via quivr-api google-cloud-vision==3.7.4 # via unstructured -googleapis-common-protos==1.63.2 +google-pasta==0.2.0 + # via sagemaker +googleapis-common-protos==1.65.0 # via google-api-core # via grpcio-status -gotrue==2.7.0 - # via supabase -greenlet==3.0.3 - # via playwright + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http +graphene==3.3 + # via mlflow +graphql-core==3.2.5 + # via graphene + # via graphql-relay +graphql-relay==3.2.0 + # via graphene +greenlet==3.1.1 # via sqlalchemy -griffe==1.2.0 - # via mkdocstrings-python -grpcio==1.65.5 +grpcio==1.67.0 # via google-api-core # via grpcio-status -grpcio-status==1.65.5 + # via opentelemetry-exporter-otlp-proto-grpc +grpcio-status==1.62.3 # via google-api-core +gunicorn==23.0.0 + # via mlflow h11==0.14.0 # via httpcore # via uvicorn -h2==4.1.0 + # via wsproto +httpcore==1.0.6 # via httpx -h5py==3.10.0 - # via python-doctr - # via quivr-diff-assistant -hpack==4.0.0 - # via h2 -httpcore==1.0.5 - # via httpx -httplib2==0.22.0 - # via google-api-python-client - # via google-auth-httplib2 -httpx==0.27.0 +httpx==0.27.2 # via anthropic + # via chainlit # via cohere - # via gotrue + # via langgraph-sdk # via langsmith + # via literalai # via llama-cloud # via llama-index-core # via llama-index-legacy - # via notion-client # via openai - # via postgrest # via quivr-core - # via storage3 - # via supabase - # via supafunc + # via unstructured-client httpx-sse==0.4.0 # via cohere -huggingface-hub==0.24.6 - # via python-doctr + # via langgraph-sdk +huggingface-hub==0.25.2 # via timm # via tokenizers # via transformers # via unstructured-inference humanfriendly==10.0 # via coloredlogs -humanize==4.10.0 - # via flower -hyperframe==6.0.1 - # via h2 -idna==3.4 +idna==3.10 # via anyio # via httpx # via requests - # via unstructured-client # via yarl -importlib-metadata==8.4.0 - # via litellm +importlib-metadata==6.11.0 + # via mlflow-skinny + # via opentelemetry-api + # via sagemaker + # via sagemaker-core iopath==0.1.10 # via layoutparser -ipykernel==6.29.5 - # via mkdocs-jupyter -ipython==8.27.0 - # via ipykernel -jedi==0.19.1 - # via ipython -jinja2==3.1.3 - # via litellm - # via mkdocs - # via mkdocs-material - # via mkdocstrings - # via nbconvert +itsdangerous==2.2.0 + # via flask +jinja2==3.1.4 + # via flask + # via mlflow # via torch -jiter==0.5.0 +jiter==0.6.1 # via anthropic # via openai jmespath==1.0.1 @@ -342,41 +273,22 @@ jsonpath-python==1.0.6 jsonpointer==3.0.0 # via jsonpatch jsonschema==4.23.0 - # via litellm - # via nbformat -jsonschema-specifications==2023.12.1 + # via sagemaker + # via sagemaker-core +jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-client==8.6.2 - # via ipykernel - # via nbclient -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client - # via nbclient - # via nbconvert - # via nbformat -jupyterlab-pygments==0.3.0 - # via nbconvert -jupytext==1.16.4 - # via mkdocs-jupyter -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib -kombu==5.4.0 - # via celery langchain==0.2.16 # via langchain-community # via megaparse - # via quivr-api # via quivr-core - # via quivr-diff-assistant langchain-anthropic==0.1.23 # via quivr-core -langchain-cohere==0.2.2 - # via quivr-api -langchain-community==0.2.12 +langchain-cohere==0.2.4 +langchain-community==0.2.17 # via langchain-experimental # via megaparse - # via quivr-api # via quivr-core langchain-core==0.2.41 # via langchain @@ -390,42 +302,40 @@ langchain-core==0.2.41 # via langgraph-checkpoint # via megaparse # via quivr-core -langchain-experimental==0.0.64 +langchain-experimental==0.0.65 # via langchain-cohere langchain-openai==0.1.25 # via megaparse - # via quivr-api - # via quivr-core - # via quivr-diff-assistant -langchain-text-splitters==0.2.2 +langchain-text-splitters==0.2.4 # via langchain langdetect==1.0.9 - # via python-doctr # via unstructured -langgraph==0.2.19 +langgraph==0.2.38 # via quivr-core -langgraph-checkpoint==1.0.9 +langgraph-checkpoint==2.0.1 # via langgraph -langsmith==0.1.126 +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 # via langchain # via langchain-community # via langchain-core layoutparser==0.3.4 # via unstructured-inference -litellm==1.43.19 - # via quivr-api -llama-cloud==0.0.13 +lazify==0.4.0 + # via chainlit +literalai==0.0.607 + # via chainlit +llama-cloud==0.1.2 # via llama-index-indices-managed-llama-cloud -llama-index==0.11.12 +llama-index==0.11.18 # via megaparse - # via quivr-core - # via quivr-diff-assistant llama-index-agent-openai==0.3.4 # via llama-index # via llama-index-program-openai llama-index-cli==0.3.1 # via llama-index -llama-index-core==0.11.12 +llama-index-core==0.11.18 # via llama-index # via llama-index-agent-openai # via llama-index-cli @@ -441,19 +351,18 @@ llama-index-core==0.11.12 llama-index-embeddings-openai==0.2.5 # via llama-index # via llama-index-cli -llama-index-indices-managed-llama-cloud==0.3.1 +llama-index-indices-managed-llama-cloud==0.4.0 # via llama-index llama-index-legacy==0.9.48.post3 # via llama-index -llama-index-llms-openai==0.2.9 +llama-index-llms-openai==0.2.15 # via llama-index # via llama-index-agent-openai # via llama-index-cli # via llama-index-multi-modal-llms-openai # via llama-index-program-openai # via llama-index-question-gen-openai - # via quivr-diff-assistant -llama-index-multi-modal-llms-openai==0.2.1 +llama-index-multi-modal-llms-openai==0.2.2 # via llama-index llama-index-program-openai==0.2.0 # via llama-index @@ -462,119 +371,62 @@ llama-index-question-gen-openai==0.2.0 # via llama-index llama-index-readers-file==0.2.2 # via llama-index - # via quivr-diff-assistant llama-index-readers-llama-parse==0.3.0 # via llama-index -llama-parse==0.5.6 +llama-parse==0.5.9 # via llama-index-readers-llama-parse # via megaparse - # via quivr-api - # via quivr-core -llvmlite==0.43.0 - # via numba lxml==5.3.0 # via pikepdf # via python-docx # via python-pptx # via unstructured +mako==1.3.5 + # via alembic mammoth==1.8.0 # via megaparse - # via quivr-core markdown==3.7 - # via mkdocs - # via mkdocs-autorefs - # via mkdocs-material - # via mkdocstrings - # via pymdown-extensions + # via mlflow # via unstructured markdown-it-py==3.0.0 - # via jupytext - # via mdit-py-plugins # via rich -markdownify==0.13.1 - # via quivr-api -markupsafe==2.1.5 +markupsafe==3.0.1 # via jinja2 - # via mkdocs - # via mkdocs-autorefs - # via mkdocstrings - # via nbconvert + # via mako + # via werkzeug marshmallow==3.22.0 # via dataclasses-json - # via marshmallow-enum - # via unstructured-client -marshmallow-enum==1.5.1 - # via unstructured-client matplotlib==3.9.2 - # via mplcursors + # via mlflow # via pycocotools - # via quivr-diff-assistant # via unstructured-inference -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython -mdit-py-plugins==0.4.1 - # via jupytext mdurl==0.1.2 # via markdown-it-py megaparse==0.0.31 # via quivr-core - # via quivr-diff-assistant -mergedeep==1.3.4 - # via mkdocs - # via mkdocs-get-deps -mistune==3.0.2 - # via nbconvert -mkdocs==1.6.1 - # via mkdocs-autorefs - # via mkdocs-include-dir-to-nav - # via mkdocs-jupyter - # via mkdocs-material - # via mkdocs-redirects - # via mkdocstrings -mkdocs-autorefs==1.2.0 - # via mkdocstrings - # via mkdocstrings-python -mkdocs-get-deps==0.2.0 - # via mkdocs -mkdocs-include-dir-to-nav==1.2.0 -mkdocs-jupyter==0.25.0 -mkdocs-material==9.5.34 - # via mkdocs-jupyter -mkdocs-material-extensions==1.3.1 - # via mkdocs-material -mkdocs-redirects==1.2.1 -mkdocstrings==0.26.1 - # via mkdocstrings-python -mkdocstrings-python==1.11.1 - # via mkdocstrings -monotonic==1.6 - # via posthog -mplcursors==0.5.3 - # via quivr-diff-assistant +mlflow==2.17.0 + # via sagemaker-mlflow +mlflow-skinny==2.17.0 + # via mlflow +mock==4.0.3 + # via sagemaker-core mpmath==1.3.0 # via sympy -msal==1.30.0 - # via quivr-api -multidict==6.0.5 +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 # via aiohttp # via yarl +multiprocess==0.70.17 + # via pathos mypy-extensions==1.0.0 # via typing-inspect - # via unstructured-client -nbclient==0.10.0 - # via nbconvert -nbconvert==7.16.4 - # via mkdocs-jupyter -nbformat==5.10.4 - # via jupytext - # via nbclient - # via nbconvert nest-asyncio==1.6.0 - # via ipykernel + # via chainlit # via llama-index-core # via llama-index-legacy -networkx==3.2.1 + # via unstructured-client +networkx==3.4.1 # via llama-index-core # via llama-index-legacy # via torch @@ -584,108 +436,143 @@ nltk==3.9.1 # via llama-index-core # via llama-index-legacy # via unstructured -notion-client==2.2.1 - # via quivr-api -numba==0.60.0 - # via quivr-diff-assistant -numpy==1.26.3 +numpy==1.26.4 + # via chainlit # via contourpy # via faiss-cpu - # via h5py # via langchain # via langchain-community # via layoutparser # via llama-index-core # via llama-index-legacy # via matplotlib - # via numba + # via mlflow # via onnx # via onnxruntime # via opencv-python # via opencv-python-headless # via pandas # via pdf2docx - # via pgvector + # via pyarrow # via pycocotools - # via python-doctr - # via quivr-diff-assistant + # via sagemaker # via scikit-learn # via scipy - # via shapely # via torchvision # via transformers # via unstructured -oauthlib==3.2.2 - # via requests-oauthlib +nvidia-cublas-cu12==12.1.3.1 + # via nvidia-cudnn-cu12 + # via nvidia-cusolver-cu12 + # via torch +nvidia-cuda-cupti-cu12==12.1.105 + # via torch +nvidia-cuda-nvrtc-cu12==12.1.105 + # via torch +nvidia-cuda-runtime-cu12==12.1.105 + # via torch +nvidia-cudnn-cu12==9.1.0.70 + # via torch +nvidia-cufft-cu12==11.0.2.54 + # via torch +nvidia-curand-cu12==10.3.2.106 + # via torch +nvidia-cusolver-cu12==11.4.5.107 + # via torch +nvidia-cusparse-cu12==12.1.0.106 + # via nvidia-cusolver-cu12 + # via torch +nvidia-nccl-cu12==2.20.5 + # via torch +nvidia-nvjitlink-cu12==12.6.77 + # via nvidia-cusolver-cu12 + # via nvidia-cusparse-cu12 +nvidia-nvtx-cu12==12.1.105 + # via torch olefile==0.47 # via python-oxmsg omegaconf==2.3.0 # via effdet -onnx==1.16.2 +onnx==1.17.0 # via unstructured # via unstructured-inference -onnxruntime==1.19.0 +onnxruntime==1.19.2 # via unstructured-inference -openai==1.47.1 +openai==1.51.2 # via langchain-openai - # via litellm # via llama-index-agent-openai # via llama-index-embeddings-openai # via llama-index-legacy # via llama-index-llms-openai - # via quivr-api - # via quivr-diff-assistant - # via quivr-worker opencv-python==4.10.0.84 # via layoutparser - # via python-doctr - # via quivr-diff-assistant # via unstructured-inference opencv-python-headless==4.10.0.84 # via pdf2docx openpyxl==3.1.5 - # via quivr-diff-assistant # via unstructured +opentelemetry-api==1.27.0 + # via mlflow-skinny + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http + # via opentelemetry-instrumentation + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions + # via uptrace +opentelemetry-exporter-otlp==1.27.0 + # via uptrace +opentelemetry-exporter-otlp-proto-common==1.27.0 + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http +opentelemetry-exporter-otlp-proto-grpc==1.27.0 + # via opentelemetry-exporter-otlp +opentelemetry-exporter-otlp-proto-http==1.27.0 + # via opentelemetry-exporter-otlp +opentelemetry-instrumentation==0.48b0 + # via uptrace +opentelemetry-proto==1.27.0 + # via opentelemetry-exporter-otlp-proto-common + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http +opentelemetry-sdk==1.27.0 + # via mlflow-skinny + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-exporter-otlp-proto-http + # via uptrace +opentelemetry-semantic-conventions==0.48b0 + # via opentelemetry-sdk orjson==3.10.7 + # via langgraph-sdk # via langsmith - # via quivr-monorepo -packaging==24.1 - # via deprecation +packaging==23.2 + # via chainlit # via faiss-cpu + # via gunicorn # via huggingface-hub - # via ipykernel - # via jupytext # via langchain-core + # via literalai # via marshmallow # via matplotlib - # via mkdocs - # via nbconvert + # via mlflow-skinny # via onnxruntime # via pikepdf - # via quivr-monorepo + # via sagemaker # via transformers - # via unstructured-client # via unstructured-pytesseract -paginate==0.5.7 - # via mkdocs-material -pandas==2.2.2 +pandas==2.2.3 # via langchain-cohere # via layoutparser # via llama-index-legacy # via llama-index-readers-file - # via quivr-diff-assistant + # via mlflow + # via sagemaker # via unstructured -pandocfilters==1.5.1 - # via nbconvert parameterized==0.9.0 # via cohere -parso==0.8.4 - # via jedi -pathspec==0.12.1 - # via mkdocs +pathos==0.3.3 + # via sagemaker pdf2docx==0.5.8 # via megaparse - # via quivr-core pdf2image==1.17.0 # via layoutparser # via unstructured @@ -695,17 +582,11 @@ pdfminer-six==20231228 pdfplumber==0.11.4 # via layoutparser # via megaparse - # via quivr-core -pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via ipython -pgvector==0.3.2 - # via quivr-api pi-heif==0.18.0 # via unstructured -pikepdf==9.1.1 +pikepdf==9.3.0 # via unstructured -pillow==10.2.0 - # via fpdf2 +pillow==11.0.0 # via layoutparser # via llama-index-core # via matplotlib @@ -713,269 +594,202 @@ pillow==10.2.0 # via pdfplumber # via pi-heif # via pikepdf - # via python-doctr # via python-pptx # via torchvision # via unstructured-pytesseract -platformdirs==4.3.2 - # via jupyter-core - # via mkdocs-get-deps - # via mkdocstrings -playwright==1.46.0 - # via quivr-worker -ply==3.11 - # via stone +platformdirs==4.3.6 + # via sagemaker + # via sagemaker-core portalocker==2.10.1 # via iopath -postgrest==0.16.10 - # via supabase -posthog==3.5.0 - # via quivr-api -prometheus-client==0.20.0 - # via flower -prompt-toolkit==3.0.47 - # via click-repl - # via ipython +pox==0.3.5 + # via pathos +ppft==1.7.6.9 + # via pathos +propcache==0.2.0 + # via yarl proto-plus==1.24.0 # via google-api-core # via google-cloud-vision -protobuf==5.27.3 +protobuf==4.25.5 # via google-api-core # via google-cloud-vision # via googleapis-common-protos # via grpcio-status + # via mlflow-skinny # via onnx # via onnxruntime + # via opentelemetry-proto # via proto-plus + # via sagemaker # via transformers psutil==6.0.0 - # via ipykernel + # via sagemaker # via unstructured -psycopg2-binary==2.9.9 - # via quivr-api -ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via pexpect -pure-eval==0.2.3 - # via stack-data -pyasn1==0.6.0 +pyarrow==17.0.0 + # via mlflow +pyasn1==0.6.1 # via pyasn1-modules - # via python-jose # via rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via google-auth -pyclipper==1.3.0.post5 - # via python-doctr pycocotools==2.0.8 # via effdet -pycparser==2.22 ; platform_python_implementation != 'PyPy' or implementation_name == 'pypy' +pycparser==2.22 # via cffi -pycryptodome==3.20.0 +pycryptodome==3.21.0 # via megaparse - # via quivr-core -pydantic==2.8.2 +pydantic==2.9.2 # via anthropic + # via chainlit # via cohere # via fastapi - # via gotrue # via langchain # via langchain-core # via langsmith - # via litellm + # via literalai # via llama-cloud # via llama-index-core # via openai - # via postgrest - # via pydantic-settings # via quivr-core + # via sagemaker-core # via sqlmodel -pydantic-core==2.20.1 + # via unstructured-client +pydantic-core==2.23.4 # via cohere # via pydantic -pydantic-settings==2.4.0 - # via quivr-api -pyee==11.1.0 - # via playwright pygments==2.18.0 - # via ipython - # via mkdocs-jupyter - # via mkdocs-material - # via nbconvert # via rich -pyinstrument==4.7.2 - # via quivr-api pyjwt==2.9.0 - # via msal -pymdown-extensions==10.9 - # via mkdocs-material - # via mkdocstrings -pymupdf==1.24.9 + # via chainlit +pymupdf==1.24.11 # via pdf2docx -pymupdfb==1.24.9 - # via pymupdf -pypandoc==1.13 +pypandoc==1.14 # via unstructured -pyparsing==3.1.2 - # via httplib2 +pyparsing==3.2.0 # via matplotlib - # via unstructured-client pypdf==4.3.1 # via llama-index-readers-file - # via quivr-diff-assistant # via unstructured + # via unstructured-client pypdfium2==4.30.0 # via pdfplumber - # via python-doctr - # via quivr-diff-assistant -pyreadline3==3.4.1 ; sys_platform == 'win32' - # via humanfriendly -python-dateutil==2.9.0.post0 +python-dateutil==2.8.2 # via botocore - # via celery - # via ghp-import - # via jupyter-client # via matplotlib # via pandas - # via posthog - # via realtime - # via storage3 # via unstructured-client -python-doctr==0.9.0 - # via quivr-diff-assistant python-docx==1.1.2 # via megaparse # via pdf2docx - # via quivr-core # via unstructured python-dotenv==1.0.1 - # via litellm + # via chainlit # via megaparse - # via pydantic-settings - # via quivr-api - # via quivr-core - # via quivr-diff-assistant - # via quivr-worker +python-engineio==4.10.1 + # via python-socketio python-iso639==2024.4.27 # via unstructured -python-jose==3.3.0 - # via quivr-api -python-json-logger==2.0.7 - # via quivr-monorepo python-magic==0.4.27 - # via quivr-diff-assistant # via unstructured python-multipart==0.0.9 - # via quivr-api + # via chainlit # via unstructured-inference python-oxmsg==0.0.1 # via unstructured python-pptx==1.0.2 # via megaparse - # via quivr-core # via unstructured -pytz==2024.1 - # via flower +python-socketio==5.11.4 + # via chainlit +pytz==2024.2 # via pandas -pywin32==306 ; (platform_python_implementation != 'PyPy' and sys_platform == 'win32') or platform_system == 'Windows' - # via jupyter-core - # via portalocker pyyaml==6.0.2 # via huggingface-hub - # via jupytext # via langchain # via langchain-community # via langchain-core # via layoutparser # via llama-index-core - # via mkdocs - # via mkdocs-get-deps + # via mlflow-skinny # via omegaconf - # via pymdown-extensions - # via pyyaml-env-tag + # via sagemaker + # via sagemaker-core # via timm # via transformers -pyyaml-env-tag==0.1 - # via mkdocs -pyzmq==26.2.0 - # via ipykernel - # via jupyter-client -rapidfuzz==3.9.6 - # via python-doctr +quivr-core==0.0.18 +rapidfuzz==3.10.0 # via unstructured # via unstructured-inference -realtime==2.0.2 - # via supabase -redis==5.0.8 - # via celery - # via quivr-api referencing==0.35.1 # via jsonschema # via jsonschema-specifications -regex==2024.7.24 - # via mkdocs-material +regex==2024.9.11 # via nltk # via tiktoken # via transformers requests==2.32.3 # via cohere - # via dropbox + # via databricks-sdk + # via docker # via google-api-core # via huggingface-hub # via langchain # via langchain-community # via langsmith - # via litellm # via llama-index-core # via llama-index-legacy - # via mkdocs-material - # via msal - # via posthog - # via requests-oauthlib - # via resend + # via mlflow-skinny + # via opentelemetry-exporter-otlp-proto-http + # via requests-toolbelt + # via sagemaker # via tiktoken # via transformers # via unstructured +requests-toolbelt==1.0.0 + # via langsmith # via unstructured-client -requests-oauthlib==2.0.0 - # via google-auth-oauthlib -resend==2.4.0 - # via quivr-api -rich==13.7.1 +rich==13.9.2 # via quivr-core + # via sagemaker-core rpds-py==0.20.0 # via jsonschema # via referencing rsa==4.9 # via google-auth - # via python-jose -s3transfer==0.10.2 +s3transfer==0.10.3 # via boto3 -safetensors==0.4.4 +safetensors==0.4.5 # via timm # via transformers +sagemaker==2.232.2 + # via cohere +sagemaker-core==1.0.10 + # via sagemaker +sagemaker-mlflow==0.1.0 + # via sagemaker +schema==0.7.7 + # via sagemaker scikit-learn==1.5.2 - # via quivr-diff-assistant + # via mlflow scipy==1.14.1 # via layoutparser - # via python-doctr + # via mlflow # via scikit-learn sentencepiece==0.2.0 # via transformers -sentry-sdk==2.13.0 - # via quivr-api -shapely==2.0.6 - # via python-doctr +setuptools==75.2.0 + # via opentelemetry-instrumentation +simple-websocket==1.1.0 + # via python-engineio six==1.16.0 - # via asttokens - # via bleach - # via dropbox - # via ecdsa - # via fire + # via google-pasta # via langdetect - # via markdownify - # via posthog # via python-dateutil - # via stone - # via unstructured-client +smdebug-rulesconfig==1.0.1 + # via sagemaker +smmap==5.0.1 + # via gitdb sniffio==1.3.1 # via anthropic # via anyio @@ -983,196 +797,145 @@ sniffio==1.3.1 # via openai soupsieve==2.6 # via beautifulsoup4 -sqlalchemy==2.0.32 +sqlalchemy==2.0.36 + # via alembic # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy + # via mlflow # via sqlmodel -sqlmodel==0.0.21 - # via quivr-api -stack-data==0.6.3 - # via ipython -starlette==0.38.2 +sqlmodel==0.0.22 +sqlparse==0.5.1 + # via mlflow-skinny +starlette==0.37.2 + # via chainlit # via fastapi -stone==3.3.1 - # via dropbox -storage3==0.7.7 - # via supabase -strenum==0.4.15 - # via postgrest striprtf==0.0.26 # via llama-index-readers-file -structlog==24.4.0 - # via quivr-monorepo -supabase==2.7.2 - # via quivr-api -supafunc==0.5.1 - # via supabase -sympy==1.12 +sympy==1.13.3 # via onnxruntime # via torch +syncer==2.0.3 + # via chainlit tabulate==0.9.0 # via langchain-cohere # via unstructured +tblib==3.0.0 + # via sagemaker tenacity==8.5.0 # via langchain # via langchain-community # via langchain-core # via llama-index-core # via llama-index-legacy -termcolor==2.4.0 +termcolor==2.5.0 # via fire threadpoolctl==3.5.0 # via scikit-learn -tiktoken==0.7.0 +tiktoken==0.8.0 # via langchain-openai - # via litellm # via llama-index-core # via llama-index-legacy - # via quivr-api # via quivr-core -timm==1.0.8 +timm==1.0.11 # via effdet # via unstructured-inference -tinycss2==1.3.0 - # via nbconvert -tokenizers==0.19.1 +tokenizers==0.20.1 # via anthropic # via cohere - # via litellm # via transformers -torch==2.4.0 ; platform_machine != 'x86_64' +tomli==2.0.2 + # via chainlit +torch==2.4.1 # via effdet - # via quivr-worker # via timm # via torchvision # via unstructured-inference -torch==2.4.0+cpu ; platform_machine == 'x86_64' +torchvision==0.19.1 # via effdet - # via quivr-worker # via timm - # via torchvision - # via unstructured-inference -torchvision==0.19.0 ; platform_machine != 'x86_64' - # via effdet - # via quivr-worker - # via timm -torchvision==0.19.0+cpu ; platform_machine == 'x86_64' - # via effdet - # via quivr-worker - # via timm -tornado==6.4.1 - # via flower - # via ipykernel - # via jupyter-client tqdm==4.66.5 # via huggingface-hub # via iopath # via llama-index-core # via nltk # via openai - # via python-doctr + # via sagemaker # via transformers # via unstructured -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline - # via nbclient - # via nbconvert - # via nbformat -transformers==4.44.2 +transformers==4.45.2 # via quivr-core # via unstructured-inference -types-pyyaml==6.0.12.20240808 +triton==3.0.0 + # via torch +types-pyyaml==6.0.12.20240917 # via quivr-core -types-requests==2.31.0.6 +types-requests==2.32.0.20241016 # via cohere -types-urllib3==1.26.25.14 - # via types-requests typing-extensions==4.12.2 + # via alembic # via anthropic # via cohere - # via emoji # via fastapi # via huggingface-hub # via iopath - # via ipython # via langchain-core # via llama-index-core # via llama-index-legacy # via openai + # via opentelemetry-sdk # via pydantic # via pydantic-core - # via pyee # via python-docx # via python-oxmsg # via python-pptx - # via realtime - # via resend # via sqlalchemy - # via storage3 # via torch # via typing-inspect # via unstructured - # via unstructured-client typing-inspect==0.9.0 # via dataclasses-json # via llama-index-core # via llama-index-legacy # via unstructured-client -tzdata==2024.1 - # via celery +tzdata==2024.2 # via pandas -unidecode==1.3.8 - # via quivr-api -unstructured==0.15.13 +unstructured==0.15.14 # via megaparse # via quivr-core - # via quivr-diff-assistant -unstructured-client==0.8.1 +unstructured-client==0.26.1 # via unstructured unstructured-inference==0.7.36 # via unstructured unstructured-pytesseract==0.3.13 # via unstructured -uritemplate==4.1.1 - # via google-api-python-client -urllib3==1.26.13 +uptrace==1.27.0 + # via chainlit +urllib3==2.2.3 # via botocore + # via docker # via requests - # via sentry-sdk - # via unstructured-client -uvicorn==0.30.6 - # via quivr-api -uvloop==0.20.0 - # via quivr-api -vine==5.1.0 - # via amqp - # via celery - # via kombu -watchdog==5.0.2 - # via mkdocs -wcwidth==0.2.13 - # via prompt-toolkit -webencodings==0.5.1 - # via bleach - # via tinycss2 -websockets==12.0 - # via realtime + # via sagemaker + # via types-requests +uvicorn==0.25.0 + # via chainlit +watchfiles==0.20.0 + # via chainlit +werkzeug==3.0.4 + # via flask wrapt==1.16.0 # via deprecated # via llama-index-core + # via opentelemetry-instrumentation # via unstructured +wsproto==1.2.0 + # via simple-websocket xlrd==2.0.1 # via unstructured xlsxwriter==3.2.0 # via python-pptx -yarl==1.9.4 +yarl==1.15.4 # via aiohttp -zipp==3.20.0 +zipp==3.20.2 # via importlib-metadata diff --git a/backend/core/examples/pdf_document_from_yaml.py b/examples/pdf_document_from_yaml.py similarity index 100% rename from backend/core/examples/pdf_document_from_yaml.py rename to examples/pdf_document_from_yaml.py diff --git a/backend/core/examples/pdf_parsing_tika.py b/examples/pdf_parsing_tika.py similarity index 100% rename from backend/core/examples/pdf_parsing_tika.py rename to examples/pdf_parsing_tika.py diff --git a/backend/core/examples/save_load_brain.py b/examples/save_load_brain.py similarity index 100% rename from backend/core/examples/save_load_brain.py rename to examples/save_load_brain.py diff --git a/backend/docs/.gitignore b/examples/simple_question/.gitignore similarity index 100% rename from backend/docs/.gitignore rename to examples/simple_question/.gitignore diff --git a/backend/worker/diff-assistant/.python-version b/examples/simple_question/.python-version similarity index 100% rename from backend/worker/diff-assistant/.python-version rename to examples/simple_question/.python-version diff --git a/backend/README.md b/examples/simple_question/README.md similarity index 61% rename from backend/README.md rename to examples/simple_question/README.md index ce78d8411..699b15aed 100644 --- a/backend/README.md +++ b/examples/simple_question/README.md @@ -1,3 +1,3 @@ -# backend +# simple-question Describe your project here. diff --git a/examples/simple_question/pyproject.toml b/examples/simple_question/pyproject.toml new file mode 100644 index 000000000..d9da7be53 --- /dev/null +++ b/examples/simple_question/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "simple-question" +version = "0.1.0" +description = "Add your description here" +authors = [ + { name = "Stan Girard", email = "stan@quivr.app" } +] +dependencies = [ + "quivr-core[all]>=0.0.18", + "sqlmodel>=0.0.22", + "langchain-cohere>=0.1.0", + "langchain-openai>=0.1.0", +] +readme = "README.md" +requires-python = ">= 3.11" + +[tool.rye] +managed = true +virtual = true +dev-dependencies = [] diff --git a/backend/worker/diff-assistant/requirements-dev.lock b/examples/simple_question/requirements-dev.lock similarity index 62% rename from backend/worker/diff-assistant/requirements-dev.lock rename to examples/simple_question/requirements-dev.lock index e957ff01d..4ec653160 100644 --- a/backend/worker/diff-assistant/requirements-dev.lock +++ b/examples/simple_question/requirements-dev.lock @@ -9,46 +9,55 @@ # generate-hashes: false # universal: false --e file:. -aiohappyeyeballs==2.4.0 +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.5 +aiohttp==3.10.10 # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy aiosignal==1.3.1 # via aiohttp -altair==5.4.1 - # via streamlit +alembic==1.13.3 + # via mlflow +aniso8601==9.0.1 + # via graphene annotated-types==0.7.0 # via pydantic +anthropic==0.36.1 + # via langchain-anthropic antlr4-python3-runtime==4.9.3 # via omegaconf -anyascii==0.3.2 - # via python-doctr -anyio==4.4.0 +anyio==4.6.2.post1 + # via anthropic # via httpx # via openai -appnope==0.1.4 - # via ipykernel -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 +attrs==23.2.0 # via aiohttp # via jsonschema # via referencing + # via sagemaker backoff==2.2.1 # via unstructured beautifulsoup4==4.12.3 # via llama-index-readers-file # via unstructured blinker==1.8.2 - # via streamlit + # via flask +boto3==1.35.42 + # via cohere + # via sagemaker + # via sagemaker-core + # via sagemaker-mlflow +botocore==1.35.42 + # via boto3 + # via s3transfer cachetools==5.5.0 # via google-auth - # via streamlit -certifi==2024.7.4 + # via mlflow-skinny +certifi==2024.8.30 # via httpcore # via httpx # via requests @@ -57,80 +66,94 @@ cffi==1.17.1 # via cryptography chardet==5.2.0 # via unstructured -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 # via pdfminer-six # via requests # via unstructured-client click==8.1.7 + # via flask + # via llama-parse + # via mlflow-skinny # via nltk # via python-oxmsg - # via streamlit +cloudpickle==2.2.1 + # via mlflow-skinny + # via sagemaker cobble==0.1.4 # via mammoth +cohere==5.11.0 + # via langchain-cohere coloredlogs==15.0.1 # via onnxruntime -comm==0.2.2 - # via ipykernel -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib cryptography==43.0.1 # via pdfminer-six + # via unstructured-client cycler==0.12.1 # via matplotlib +databricks-sdk==0.34.0 + # via mlflow-skinny dataclasses-json==0.6.7 # via langchain-community # via llama-index-core # via llama-index-legacy # via unstructured # via unstructured-client -debugpy==1.8.5 - # via ipykernel -decorator==5.1.1 - # via ipython -deepdiff==7.0.1 +deepdiff==8.0.1 # via unstructured-client defusedxml==0.7.1 - # via python-doctr + # via langchain-anthropic deprecated==1.2.14 # via llama-index-core # via llama-index-legacy + # via opentelemetry-api + # via opentelemetry-semantic-conventions # via pikepdf -diff-match-patch==20230430 - # via diff-assistant +dill==0.3.9 + # via multiprocess + # via pathos dirtyjson==1.0.8 # via llama-index-core # via llama-index-legacy distro==1.9.0 + # via anthropic # via openai +docker==7.1.0 + # via mlflow + # via sagemaker docx2txt==0.8 - # via diff-assistant + # via quivr-core effdet==0.4.1 # via unstructured -emoji==2.12.1 +emoji==2.14.0 # via unstructured et-xmlfile==1.1.0 # via openpyxl -executing==2.0.1 - # via stack-data -faiss-cpu==1.8.0.post1 - # via diff-assistant -filelock==3.15.4 +faiss-cpu==1.9.0 + # via quivr-core +fastavro==1.9.7 + # via cohere +filelock==3.16.1 # via huggingface-hub # via torch # via transformers + # via triton filetype==1.2.0 # via unstructured -fire==0.6.0 +fire==0.7.0 # via pdf2docx +flask==3.0.3 + # via mlflow flatbuffers==24.3.25 # via onnxruntime -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib # via pdf2docx frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.6.1 +fsspec==2024.9.0 # via huggingface-hub # via llama-index-core # via llama-index-legacy @@ -138,67 +161,86 @@ fsspec==2024.6.1 gitdb==4.0.11 # via gitpython gitpython==3.1.43 - # via streamlit -google-api-core==2.19.2 + # via mlflow-skinny +google-api-core==2.21.0 # via google-cloud-vision -google-auth==2.34.0 +google-auth==2.35.0 + # via databricks-sdk # via google-api-core # via google-cloud-vision google-cloud-vision==3.7.4 # via unstructured +google-pasta==0.2.0 + # via sagemaker googleapis-common-protos==1.65.0 # via google-api-core # via grpcio-status -greenlet==3.0.3 +graphene==3.3 + # via mlflow +graphql-core==3.2.5 + # via graphene + # via graphql-relay +graphql-relay==3.2.0 + # via graphene +greenlet==3.1.1 # via sqlalchemy -grpcio==1.66.1 +grpcio==1.67.0 # via google-api-core # via grpcio-status -grpcio-status==1.66.1 +grpcio-status==1.62.3 # via google-api-core +gunicorn==23.0.0 + # via mlflow h11==0.14.0 # via httpcore -h5py==3.11.0 - # via python-doctr -httpcore==1.0.5 +httpcore==1.0.6 # via httpx -httpx==0.27.0 +httpx==0.27.2 + # via anthropic + # via cohere + # via langgraph-sdk # via langsmith # via llama-cloud # via llama-index-core # via llama-index-legacy # via openai + # via quivr-core # via unstructured-client -huggingface-hub==0.24.6 - # via python-doctr +httpx-sse==0.4.0 + # via cohere + # via langgraph-sdk +huggingface-hub==0.25.2 # via timm # via tokenizers # via transformers # via unstructured-inference humanfriendly==10.0 # via coloredlogs -idna==3.7 +idna==3.10 # via anyio # via httpx # via requests # via unstructured-client # via yarl -iniconfig==2.0.0 - # via pytest +importlib-metadata==6.11.0 + # via mlflow-skinny + # via opentelemetry-api + # via sagemaker + # via sagemaker-core iopath==0.1.10 # via layoutparser -ipykernel==6.29.5 - # via diff-assistant -ipython==8.26.0 - # via ipykernel -jedi==0.19.1 - # via ipython +itsdangerous==2.2.0 + # via flask jinja2==3.1.4 - # via altair - # via pydeck + # via flask + # via mlflow # via torch -jiter==0.5.0 +jiter==0.6.1 + # via anthropic # via openai +jmespath==1.0.1 + # via boto3 + # via botocore joblib==1.4.2 # via nltk # via scikit-learn @@ -209,54 +251,65 @@ jsonpath-python==1.0.6 jsonpointer==3.0.0 # via jsonpatch jsonschema==4.23.0 - # via altair -jsonschema-specifications==2023.12.1 + # via sagemaker + # via sagemaker-core +jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-client==8.6.2 - # via ipykernel -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib langchain==0.2.16 - # via diff-assistant # via langchain-community # via megaparse -langchain-community==0.2.16 + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-cohere==0.2.4 +langchain-community==0.2.17 + # via langchain-experimental # via megaparse -langchain-core==0.2.39 + # via quivr-core +langchain-core==0.2.41 # via langchain + # via langchain-anthropic + # via langchain-cohere # via langchain-community + # via langchain-experimental # via langchain-openai # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint # via megaparse -langchain-openai==0.1.24 - # via diff-assistant + # via quivr-core +langchain-experimental==0.0.65 + # via langchain-cohere +langchain-openai==0.1.25 # via megaparse langchain-text-splitters==0.2.4 # via langchain langdetect==1.0.9 - # via python-doctr # via unstructured -langsmith==0.1.118 +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 # via langchain # via langchain-community # via langchain-core layoutparser==0.3.4 # via unstructured-inference -llama-cloud==0.0.17 +llama-cloud==0.1.2 # via llama-index-indices-managed-llama-cloud -llama-index==0.11.8 - # via diff-assistant +llama-index==0.11.18 # via megaparse -llama-index-agent-openai==0.3.1 +llama-index-agent-openai==0.3.4 # via llama-index - # via llama-index-llms-openai # via llama-index-program-openai llama-index-cli==0.3.1 # via llama-index -llama-index-core==0.11.8 +llama-index-core==0.11.18 # via llama-index # via llama-index-agent-openai # via llama-index-cli @@ -269,104 +322,105 @@ llama-index-core==0.11.8 # via llama-index-readers-file # via llama-index-readers-llama-parse # via llama-parse -llama-index-embeddings-openai==0.2.4 +llama-index-embeddings-openai==0.2.5 # via llama-index # via llama-index-cli -llama-index-indices-managed-llama-cloud==0.3.0 +llama-index-indices-managed-llama-cloud==0.4.0 # via llama-index llama-index-legacy==0.9.48.post3 # via llama-index -llama-index-llms-openai==0.2.3 - # via diff-assistant +llama-index-llms-openai==0.2.15 # via llama-index # via llama-index-agent-openai # via llama-index-cli # via llama-index-multi-modal-llms-openai # via llama-index-program-openai # via llama-index-question-gen-openai -llama-index-multi-modal-llms-openai==0.2.0 +llama-index-multi-modal-llms-openai==0.2.2 # via llama-index llama-index-program-openai==0.2.0 # via llama-index # via llama-index-question-gen-openai llama-index-question-gen-openai==0.2.0 # via llama-index -llama-index-readers-file==0.2.1 - # via diff-assistant +llama-index-readers-file==0.2.2 # via llama-index llama-index-readers-llama-parse==0.3.0 # via llama-index -llama-parse==0.5.3 +llama-parse==0.5.10 # via llama-index-readers-llama-parse # via megaparse -llvmlite==0.43.0 - # via numba lxml==5.3.0 # via pikepdf # via python-docx # via python-pptx # via unstructured +mako==1.3.5 + # via alembic mammoth==1.8.0 # via megaparse +markdown==3.7 + # via mlflow + # via unstructured markdown-it-py==3.0.0 # via rich -markupsafe==2.1.5 +markupsafe==3.0.1 # via jinja2 + # via mako + # via werkzeug marshmallow==3.22.0 # via dataclasses-json # via unstructured-client matplotlib==3.9.2 - # via diff-assistant - # via mplcursors + # via mlflow # via pycocotools # via unstructured-inference -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython mdurl==0.1.2 # via markdown-it-py megaparse==0.0.31 - # via diff-assistant -mplcursors==0.5.3 - # via diff-assistant + # via quivr-core +mlflow==2.17.0 + # via sagemaker-mlflow +mlflow-skinny==2.17.0 + # via mlflow +mock==4.0.3 + # via sagemaker-core mpmath==1.3.0 # via sympy -multidict==6.0.5 +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 # via aiohttp # via yarl +multiprocess==0.70.17 + # via pathos mypy-extensions==1.0.0 # via typing-inspect # via unstructured-client -narwhals==1.6.2 - # via altair nest-asyncio==1.6.0 - # via ipykernel # via llama-index-core # via llama-index-legacy # via unstructured-client -networkx==3.3 +networkx==3.4.1 # via llama-index-core # via llama-index-legacy # via torch + # via unstructured nltk==3.9.1 # via llama-index # via llama-index-core # via llama-index-legacy # via unstructured -numba==0.60.0 - # via diff-assistant numpy==1.26.4 # via contourpy - # via diff-assistant # via faiss-cpu - # via h5py # via langchain # via langchain-community # via layoutparser # via llama-index-core # via llama-index-legacy # via matplotlib - # via numba + # via mlflow # via onnx # via onnxruntime # via opencv-python @@ -375,68 +429,101 @@ numpy==1.26.4 # via pdf2docx # via pyarrow # via pycocotools - # via pydeck - # via python-doctr + # via sagemaker # via scikit-learn # via scipy - # via shapely - # via streamlit # via torchvision # via transformers # via unstructured +nvidia-cublas-cu12==12.1.3.1 + # via nvidia-cudnn-cu12 + # via nvidia-cusolver-cu12 + # via torch +nvidia-cuda-cupti-cu12==12.1.105 + # via torch +nvidia-cuda-nvrtc-cu12==12.1.105 + # via torch +nvidia-cuda-runtime-cu12==12.1.105 + # via torch +nvidia-cudnn-cu12==9.1.0.70 + # via torch +nvidia-cufft-cu12==11.0.2.54 + # via torch +nvidia-curand-cu12==10.3.2.106 + # via torch +nvidia-cusolver-cu12==11.4.5.107 + # via torch +nvidia-cusparse-cu12==12.1.0.106 + # via nvidia-cusolver-cu12 + # via torch +nvidia-nccl-cu12==2.20.5 + # via torch +nvidia-nvjitlink-cu12==12.6.77 + # via nvidia-cusolver-cu12 + # via nvidia-cusparse-cu12 +nvidia-nvtx-cu12==12.1.105 + # via torch olefile==0.47 # via python-oxmsg omegaconf==2.3.0 # via effdet -onnx==1.16.2 - # via python-doctr +onnx==1.17.0 # via unstructured # via unstructured-inference onnxruntime==1.19.2 # via unstructured-inference -openai==1.44.1 - # via diff-assistant +openai==1.51.2 # via langchain-openai # via llama-index-agent-openai # via llama-index-embeddings-openai # via llama-index-legacy # via llama-index-llms-openai opencv-python==4.10.0.84 - # via diff-assistant # via layoutparser - # via python-doctr # via unstructured-inference opencv-python-headless==4.10.0.84 # via pdf2docx openpyxl==3.1.5 - # via diff-assistant -ordered-set==4.1.0 + # via unstructured +opentelemetry-api==1.27.0 + # via mlflow-skinny + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions +opentelemetry-sdk==1.27.0 + # via mlflow-skinny +opentelemetry-semantic-conventions==0.48b0 + # via opentelemetry-sdk +orderly-set==5.2.2 # via deepdiff orjson==3.10.7 + # via langgraph-sdk # via langsmith packaging==24.1 - # via altair # via faiss-cpu + # via gunicorn # via huggingface-hub - # via ipykernel # via langchain-core # via marshmallow # via matplotlib + # via mlflow-skinny # via onnxruntime # via pikepdf - # via pytest - # via streamlit + # via sagemaker # via transformers # via unstructured-client # via unstructured-pytesseract -pandas==2.2.2 - # via diff-assistant +pandas==2.2.3 + # via langchain-cohere # via layoutparser # via llama-index-legacy # via llama-index-readers-file - # via streamlit -parso==0.8.4 - # via jedi + # via mlflow + # via sagemaker + # via unstructured +parameterized==0.9.0 + # via cohere +pathos==0.3.3 + # via sagemaker pdf2docx==0.5.8 # via megaparse pdf2image==1.17.0 @@ -448,13 +535,11 @@ pdfminer-six==20231228 pdfplumber==0.11.4 # via layoutparser # via megaparse -pexpect==4.9.0 - # via ipython pi-heif==0.18.0 # via unstructured -pikepdf==9.2.1 +pikepdf==9.3.0 # via unstructured -pillow==10.4.0 +pillow==11.0.0 # via layoutparser # via llama-index-core # via matplotlib @@ -462,108 +547,102 @@ pillow==10.4.0 # via pdfplumber # via pi-heif # via pikepdf - # via python-doctr # via python-pptx - # via streamlit # via torchvision # via unstructured-pytesseract -platformdirs==4.2.2 - # via jupyter-core -pluggy==1.5.0 - # via pytest +platformdirs==4.3.6 + # via sagemaker + # via sagemaker-core portalocker==2.10.1 # via iopath -prompt-toolkit==3.0.47 - # via ipython +pox==0.3.5 + # via pathos +ppft==1.7.6.9 + # via pathos +propcache==0.2.0 + # via yarl proto-plus==1.24.0 # via google-api-core # via google-cloud-vision -protobuf==5.27.3 +protobuf==4.25.5 # via google-api-core # via google-cloud-vision # via googleapis-common-protos # via grpcio-status + # via mlflow-skinny # via onnx # via onnxruntime # via proto-plus - # via streamlit + # via sagemaker + # via transformers psutil==6.0.0 - # via ipykernel + # via sagemaker # via unstructured -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data pyarrow==17.0.0 - # via streamlit + # via mlflow pyasn1==0.6.1 # via pyasn1-modules # via rsa pyasn1-modules==0.4.1 # via google-auth -pyclipper==1.3.0.post5 - # via python-doctr pycocotools==2.0.8 # via effdet pycparser==2.22 # via cffi -pycryptodome==3.20.0 +pycryptodome==3.21.0 # via megaparse -pydantic==2.8.2 +pydantic==2.9.2 + # via anthropic + # via cohere # via langchain # via langchain-core # via langsmith # via llama-cloud # via llama-index-core # via openai -pydantic-core==2.20.1 + # via quivr-core + # via sagemaker-core + # via sqlmodel +pydantic-core==2.23.4 + # via cohere # via pydantic -pydeck==0.9.1 - # via streamlit pygments==2.18.0 - # via ipython # via rich -pymupdf==1.24.10 +pymupdf==1.24.11 # via pdf2docx -pymupdfb==1.24.10 - # via pymupdf -pyparsing==3.1.2 +pypandoc==1.14 + # via unstructured +pyparsing==3.2.0 # via matplotlib pypdf==4.3.1 - # via diff-assistant # via llama-index-readers-file # via unstructured # via unstructured-client pypdfium2==4.30.0 - # via diff-assistant # via pdfplumber - # via python-doctr -pytest==8.3.2 python-dateutil==2.9.0.post0 - # via jupyter-client + # via botocore # via matplotlib # via pandas # via unstructured-client -python-doctr==0.9.0 - # via diff-assistant python-docx==1.1.2 # via megaparse # via pdf2docx + # via unstructured python-dotenv==1.0.1 - # via diff-assistant # via megaparse python-iso639==2024.4.27 # via unstructured python-magic==0.4.27 - # via diff-assistant # via unstructured -python-multipart==0.0.9 +python-multipart==0.0.12 # via unstructured-inference python-oxmsg==0.0.1 # via unstructured python-pptx==1.0.2 # via megaparse -pytz==2024.1 + # via unstructured +pytz==2024.2 # via pandas pyyaml==6.0.2 # via huggingface-hub @@ -572,24 +651,27 @@ pyyaml==6.0.2 # via langchain-core # via layoutparser # via llama-index-core + # via mlflow-skinny # via omegaconf + # via sagemaker + # via sagemaker-core # via timm # via transformers -pyzmq==26.1.1 - # via ipykernel - # via jupyter-client -rapidfuzz==3.9.6 - # via python-doctr +quivr-core==0.0.18 +rapidfuzz==3.10.0 # via unstructured # via unstructured-inference referencing==0.35.1 # via jsonschema # via jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via nltk # via tiktoken # via transformers requests==2.32.3 + # via cohere + # via databricks-sdk + # via docker # via google-api-core # via huggingface-hub # via langchain @@ -597,134 +679,147 @@ requests==2.32.3 # via langsmith # via llama-index-core # via llama-index-legacy + # via mlflow-skinny # via requests-toolbelt - # via streamlit + # via sagemaker # via tiktoken # via transformers # via unstructured # via unstructured-client requests-toolbelt==1.0.0 + # via langsmith # via unstructured-client -rich==13.8.0 - # via streamlit +rich==13.9.2 + # via quivr-core + # via sagemaker-core rpds-py==0.20.0 # via jsonschema # via referencing rsa==4.9 # via google-auth +s3transfer==0.10.3 + # via boto3 safetensors==0.4.5 # via timm # via transformers -scikit-learn==1.5.1 - # via diff-assistant +sagemaker==2.232.2 + # via cohere +sagemaker-core==1.0.10 + # via sagemaker +sagemaker-mlflow==0.1.0 + # via sagemaker +schema==0.7.7 + # via sagemaker +scikit-learn==1.5.2 + # via mlflow scipy==1.14.1 # via layoutparser - # via python-doctr + # via mlflow # via scikit-learn -shapely==2.0.6 - # via python-doctr +sentencepiece==0.2.0 + # via transformers six==1.16.0 - # via asttokens - # via fire + # via google-pasta # via langdetect # via python-dateutil # via unstructured-client +smdebug-rulesconfig==1.0.1 + # via sagemaker smmap==5.0.1 # via gitdb sniffio==1.3.1 + # via anthropic # via anyio # via httpx # via openai soupsieve==2.6 # via beautifulsoup4 -sqlalchemy==2.0.32 +sqlalchemy==2.0.36 + # via alembic # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy -stack-data==0.6.3 - # via ipython -streamlit==1.38.0 - # via diff-assistant + # via mlflow + # via sqlmodel +sqlmodel==0.0.22 +sqlparse==0.5.1 + # via mlflow-skinny striprtf==0.0.26 # via llama-index-readers-file -sympy==1.13.2 +sympy==1.13.3 # via onnxruntime # via torch tabulate==0.9.0 + # via langchain-cohere # via unstructured +tblib==3.0.0 + # via sagemaker tenacity==8.5.0 # via langchain # via langchain-community # via langchain-core # via llama-index-core # via llama-index-legacy - # via streamlit -termcolor==2.4.0 +termcolor==2.5.0 # via fire threadpoolctl==3.5.0 # via scikit-learn -tiktoken==0.7.0 +tiktoken==0.8.0 # via langchain-openai # via llama-index-core # via llama-index-legacy -timm==1.0.9 + # via quivr-core +timm==1.0.11 # via effdet # via unstructured-inference -tokenizers==0.19.1 +tokenizers==0.20.1 + # via anthropic + # via cohere # via transformers -toml==0.10.2 - # via streamlit -torch==2.3.1 - # via diff-assistant +torch==2.4.1 # via effdet - # via python-doctr # via timm # via torchvision # via unstructured-inference -torchvision==0.18.1 +torchvision==0.19.1 # via effdet - # via python-doctr # via timm -tornado==6.4.1 - # via ipykernel - # via jupyter-client - # via streamlit tqdm==4.66.5 # via huggingface-hub # via iopath # via llama-index-core # via nltk # via openai - # via python-doctr + # via sagemaker # via transformers # via unstructured -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline -transformers==4.44.2 +transformers==4.45.2 + # via quivr-core # via unstructured-inference +triton==3.0.0 + # via torch +types-pyyaml==6.0.12.20240917 + # via quivr-core +types-requests==2.32.0.20241016 + # via cohere typing-extensions==4.12.2 - # via altair - # via emoji + # via alembic + # via anthropic + # via cohere # via huggingface-hub # via iopath - # via ipython # via langchain-core # via llama-index-core # via llama-index-legacy # via openai + # via opentelemetry-sdk # via pydantic # via pydantic-core # via python-docx # via python-oxmsg # via python-pptx # via sqlalchemy - # via streamlit # via torch # via typing-inspect # via unstructured @@ -734,27 +829,35 @@ typing-inspect==0.9.0 # via llama-index-core # via llama-index-legacy # via unstructured-client -tzdata==2024.1 +tzdata==2024.2 # via pandas -unstructured==0.15.9 - # via diff-assistant +unstructured==0.15.14 # via megaparse -unstructured-client==0.25.5 + # via quivr-core +unstructured-client==0.25.9 # via unstructured unstructured-inference==0.7.36 # via unstructured unstructured-pytesseract==0.3.13 # via unstructured -urllib3==2.2.2 +urllib3==2.2.3 + # via botocore + # via docker # via requests + # via sagemaker + # via types-requests # via unstructured-client -wcwidth==0.2.13 - # via prompt-toolkit +werkzeug==3.0.4 + # via flask wrapt==1.16.0 # via deprecated # via llama-index-core # via unstructured +xlrd==2.0.1 + # via unstructured xlsxwriter==3.2.0 # via python-pptx -yarl==1.9.7 +yarl==1.15.4 # via aiohttp +zipp==3.20.2 + # via importlib-metadata diff --git a/backend/worker/diff-assistant/requirements.lock b/examples/simple_question/requirements.lock similarity index 62% rename from backend/worker/diff-assistant/requirements.lock rename to examples/simple_question/requirements.lock index 421906344..4ec653160 100644 --- a/backend/worker/diff-assistant/requirements.lock +++ b/examples/simple_question/requirements.lock @@ -9,46 +9,55 @@ # generate-hashes: false # universal: false --e file:. -aiohappyeyeballs==2.4.0 +aiofiles==24.1.0 + # via quivr-core +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.5 +aiohttp==3.10.10 # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy aiosignal==1.3.1 # via aiohttp -altair==5.4.1 - # via streamlit +alembic==1.13.3 + # via mlflow +aniso8601==9.0.1 + # via graphene annotated-types==0.7.0 # via pydantic +anthropic==0.36.1 + # via langchain-anthropic antlr4-python3-runtime==4.9.3 # via omegaconf -anyascii==0.3.2 - # via python-doctr -anyio==4.4.0 +anyio==4.6.2.post1 + # via anthropic # via httpx # via openai -appnope==0.1.4 - # via ipykernel -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 +attrs==23.2.0 # via aiohttp # via jsonschema # via referencing + # via sagemaker backoff==2.2.1 # via unstructured beautifulsoup4==4.12.3 # via llama-index-readers-file # via unstructured blinker==1.8.2 - # via streamlit + # via flask +boto3==1.35.42 + # via cohere + # via sagemaker + # via sagemaker-core + # via sagemaker-mlflow +botocore==1.35.42 + # via boto3 + # via s3transfer cachetools==5.5.0 # via google-auth - # via streamlit -certifi==2024.7.4 + # via mlflow-skinny +certifi==2024.8.30 # via httpcore # via httpx # via requests @@ -57,80 +66,94 @@ cffi==1.17.1 # via cryptography chardet==5.2.0 # via unstructured -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 # via pdfminer-six # via requests # via unstructured-client click==8.1.7 + # via flask + # via llama-parse + # via mlflow-skinny # via nltk # via python-oxmsg - # via streamlit +cloudpickle==2.2.1 + # via mlflow-skinny + # via sagemaker cobble==0.1.4 # via mammoth +cohere==5.11.0 + # via langchain-cohere coloredlogs==15.0.1 # via onnxruntime -comm==0.2.2 - # via ipykernel -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib cryptography==43.0.1 # via pdfminer-six + # via unstructured-client cycler==0.12.1 # via matplotlib +databricks-sdk==0.34.0 + # via mlflow-skinny dataclasses-json==0.6.7 # via langchain-community # via llama-index-core # via llama-index-legacy # via unstructured # via unstructured-client -debugpy==1.8.5 - # via ipykernel -decorator==5.1.1 - # via ipython -deepdiff==7.0.1 +deepdiff==8.0.1 # via unstructured-client defusedxml==0.7.1 - # via python-doctr + # via langchain-anthropic deprecated==1.2.14 # via llama-index-core # via llama-index-legacy + # via opentelemetry-api + # via opentelemetry-semantic-conventions # via pikepdf -diff-match-patch==20230430 - # via diff-assistant +dill==0.3.9 + # via multiprocess + # via pathos dirtyjson==1.0.8 # via llama-index-core # via llama-index-legacy distro==1.9.0 + # via anthropic # via openai +docker==7.1.0 + # via mlflow + # via sagemaker docx2txt==0.8 - # via diff-assistant + # via quivr-core effdet==0.4.1 # via unstructured -emoji==2.12.1 +emoji==2.14.0 # via unstructured et-xmlfile==1.1.0 # via openpyxl -executing==2.0.1 - # via stack-data -faiss-cpu==1.8.0.post1 - # via diff-assistant -filelock==3.15.4 +faiss-cpu==1.9.0 + # via quivr-core +fastavro==1.9.7 + # via cohere +filelock==3.16.1 # via huggingface-hub # via torch # via transformers + # via triton filetype==1.2.0 # via unstructured -fire==0.6.0 +fire==0.7.0 # via pdf2docx +flask==3.0.3 + # via mlflow flatbuffers==24.3.25 # via onnxruntime -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib # via pdf2docx frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.6.1 +fsspec==2024.9.0 # via huggingface-hub # via llama-index-core # via llama-index-legacy @@ -138,65 +161,86 @@ fsspec==2024.6.1 gitdb==4.0.11 # via gitpython gitpython==3.1.43 - # via streamlit -google-api-core==2.19.2 + # via mlflow-skinny +google-api-core==2.21.0 # via google-cloud-vision -google-auth==2.34.0 +google-auth==2.35.0 + # via databricks-sdk # via google-api-core # via google-cloud-vision google-cloud-vision==3.7.4 # via unstructured +google-pasta==0.2.0 + # via sagemaker googleapis-common-protos==1.65.0 # via google-api-core # via grpcio-status -greenlet==3.0.3 +graphene==3.3 + # via mlflow +graphql-core==3.2.5 + # via graphene + # via graphql-relay +graphql-relay==3.2.0 + # via graphene +greenlet==3.1.1 # via sqlalchemy -grpcio==1.66.1 +grpcio==1.67.0 # via google-api-core # via grpcio-status -grpcio-status==1.66.1 +grpcio-status==1.62.3 # via google-api-core +gunicorn==23.0.0 + # via mlflow h11==0.14.0 # via httpcore -h5py==3.11.0 - # via python-doctr -httpcore==1.0.5 +httpcore==1.0.6 # via httpx -httpx==0.27.0 +httpx==0.27.2 + # via anthropic + # via cohere + # via langgraph-sdk # via langsmith # via llama-cloud # via llama-index-core # via llama-index-legacy # via openai + # via quivr-core # via unstructured-client -huggingface-hub==0.24.6 - # via python-doctr +httpx-sse==0.4.0 + # via cohere + # via langgraph-sdk +huggingface-hub==0.25.2 # via timm # via tokenizers # via transformers # via unstructured-inference humanfriendly==10.0 # via coloredlogs -idna==3.7 +idna==3.10 # via anyio # via httpx # via requests # via unstructured-client # via yarl +importlib-metadata==6.11.0 + # via mlflow-skinny + # via opentelemetry-api + # via sagemaker + # via sagemaker-core iopath==0.1.10 # via layoutparser -ipykernel==6.29.5 - # via diff-assistant -ipython==8.26.0 - # via ipykernel -jedi==0.19.1 - # via ipython +itsdangerous==2.2.0 + # via flask jinja2==3.1.4 - # via altair - # via pydeck + # via flask + # via mlflow # via torch -jiter==0.5.0 +jiter==0.6.1 + # via anthropic # via openai +jmespath==1.0.1 + # via boto3 + # via botocore joblib==1.4.2 # via nltk # via scikit-learn @@ -207,54 +251,65 @@ jsonpath-python==1.0.6 jsonpointer==3.0.0 # via jsonpatch jsonschema==4.23.0 - # via altair -jsonschema-specifications==2023.12.1 + # via sagemaker + # via sagemaker-core +jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-client==8.6.2 - # via ipykernel -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib langchain==0.2.16 - # via diff-assistant # via langchain-community # via megaparse -langchain-community==0.2.16 + # via quivr-core +langchain-anthropic==0.1.23 + # via quivr-core +langchain-cohere==0.2.4 +langchain-community==0.2.17 + # via langchain-experimental # via megaparse -langchain-core==0.2.39 + # via quivr-core +langchain-core==0.2.41 # via langchain + # via langchain-anthropic + # via langchain-cohere # via langchain-community + # via langchain-experimental # via langchain-openai # via langchain-text-splitters + # via langgraph + # via langgraph-checkpoint # via megaparse -langchain-openai==0.1.24 - # via diff-assistant + # via quivr-core +langchain-experimental==0.0.65 + # via langchain-cohere +langchain-openai==0.1.25 # via megaparse langchain-text-splitters==0.2.4 # via langchain langdetect==1.0.9 - # via python-doctr # via unstructured -langsmith==0.1.118 +langgraph==0.2.38 + # via quivr-core +langgraph-checkpoint==2.0.1 + # via langgraph +langgraph-sdk==0.1.33 + # via langgraph +langsmith==0.1.135 # via langchain # via langchain-community # via langchain-core layoutparser==0.3.4 # via unstructured-inference -llama-cloud==0.0.17 +llama-cloud==0.1.2 # via llama-index-indices-managed-llama-cloud -llama-index==0.11.8 - # via diff-assistant +llama-index==0.11.18 # via megaparse -llama-index-agent-openai==0.3.1 +llama-index-agent-openai==0.3.4 # via llama-index - # via llama-index-llms-openai # via llama-index-program-openai llama-index-cli==0.3.1 # via llama-index -llama-index-core==0.11.8 +llama-index-core==0.11.18 # via llama-index # via llama-index-agent-openai # via llama-index-cli @@ -267,104 +322,105 @@ llama-index-core==0.11.8 # via llama-index-readers-file # via llama-index-readers-llama-parse # via llama-parse -llama-index-embeddings-openai==0.2.4 +llama-index-embeddings-openai==0.2.5 # via llama-index # via llama-index-cli -llama-index-indices-managed-llama-cloud==0.3.0 +llama-index-indices-managed-llama-cloud==0.4.0 # via llama-index llama-index-legacy==0.9.48.post3 # via llama-index -llama-index-llms-openai==0.2.3 - # via diff-assistant +llama-index-llms-openai==0.2.15 # via llama-index # via llama-index-agent-openai # via llama-index-cli # via llama-index-multi-modal-llms-openai # via llama-index-program-openai # via llama-index-question-gen-openai -llama-index-multi-modal-llms-openai==0.2.0 +llama-index-multi-modal-llms-openai==0.2.2 # via llama-index llama-index-program-openai==0.2.0 # via llama-index # via llama-index-question-gen-openai llama-index-question-gen-openai==0.2.0 # via llama-index -llama-index-readers-file==0.2.1 - # via diff-assistant +llama-index-readers-file==0.2.2 # via llama-index llama-index-readers-llama-parse==0.3.0 # via llama-index -llama-parse==0.5.3 +llama-parse==0.5.10 # via llama-index-readers-llama-parse # via megaparse -llvmlite==0.43.0 - # via numba lxml==5.3.0 # via pikepdf # via python-docx # via python-pptx # via unstructured +mako==1.3.5 + # via alembic mammoth==1.8.0 # via megaparse +markdown==3.7 + # via mlflow + # via unstructured markdown-it-py==3.0.0 # via rich -markupsafe==2.1.5 +markupsafe==3.0.1 # via jinja2 + # via mako + # via werkzeug marshmallow==3.22.0 # via dataclasses-json # via unstructured-client matplotlib==3.9.2 - # via diff-assistant - # via mplcursors + # via mlflow # via pycocotools # via unstructured-inference -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython mdurl==0.1.2 # via markdown-it-py megaparse==0.0.31 - # via diff-assistant -mplcursors==0.5.3 - # via diff-assistant + # via quivr-core +mlflow==2.17.0 + # via sagemaker-mlflow +mlflow-skinny==2.17.0 + # via mlflow +mock==4.0.3 + # via sagemaker-core mpmath==1.3.0 # via sympy -multidict==6.0.5 +msgpack==1.1.0 + # via langgraph-checkpoint +multidict==6.1.0 # via aiohttp # via yarl +multiprocess==0.70.17 + # via pathos mypy-extensions==1.0.0 # via typing-inspect # via unstructured-client -narwhals==1.6.2 - # via altair nest-asyncio==1.6.0 - # via ipykernel # via llama-index-core # via llama-index-legacy # via unstructured-client -networkx==3.3 +networkx==3.4.1 # via llama-index-core # via llama-index-legacy # via torch + # via unstructured nltk==3.9.1 # via llama-index # via llama-index-core # via llama-index-legacy # via unstructured -numba==0.60.0 - # via diff-assistant numpy==1.26.4 # via contourpy - # via diff-assistant # via faiss-cpu - # via h5py # via langchain # via langchain-community # via layoutparser # via llama-index-core # via llama-index-legacy # via matplotlib - # via numba + # via mlflow # via onnx # via onnxruntime # via opencv-python @@ -373,67 +429,101 @@ numpy==1.26.4 # via pdf2docx # via pyarrow # via pycocotools - # via pydeck - # via python-doctr + # via sagemaker # via scikit-learn # via scipy - # via shapely - # via streamlit # via torchvision # via transformers # via unstructured +nvidia-cublas-cu12==12.1.3.1 + # via nvidia-cudnn-cu12 + # via nvidia-cusolver-cu12 + # via torch +nvidia-cuda-cupti-cu12==12.1.105 + # via torch +nvidia-cuda-nvrtc-cu12==12.1.105 + # via torch +nvidia-cuda-runtime-cu12==12.1.105 + # via torch +nvidia-cudnn-cu12==9.1.0.70 + # via torch +nvidia-cufft-cu12==11.0.2.54 + # via torch +nvidia-curand-cu12==10.3.2.106 + # via torch +nvidia-cusolver-cu12==11.4.5.107 + # via torch +nvidia-cusparse-cu12==12.1.0.106 + # via nvidia-cusolver-cu12 + # via torch +nvidia-nccl-cu12==2.20.5 + # via torch +nvidia-nvjitlink-cu12==12.6.77 + # via nvidia-cusolver-cu12 + # via nvidia-cusparse-cu12 +nvidia-nvtx-cu12==12.1.105 + # via torch olefile==0.47 # via python-oxmsg omegaconf==2.3.0 # via effdet -onnx==1.16.2 - # via python-doctr +onnx==1.17.0 # via unstructured # via unstructured-inference onnxruntime==1.19.2 # via unstructured-inference -openai==1.44.1 - # via diff-assistant +openai==1.51.2 # via langchain-openai # via llama-index-agent-openai # via llama-index-embeddings-openai # via llama-index-legacy # via llama-index-llms-openai opencv-python==4.10.0.84 - # via diff-assistant # via layoutparser - # via python-doctr # via unstructured-inference opencv-python-headless==4.10.0.84 # via pdf2docx openpyxl==3.1.5 - # via diff-assistant -ordered-set==4.1.0 + # via unstructured +opentelemetry-api==1.27.0 + # via mlflow-skinny + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions +opentelemetry-sdk==1.27.0 + # via mlflow-skinny +opentelemetry-semantic-conventions==0.48b0 + # via opentelemetry-sdk +orderly-set==5.2.2 # via deepdiff orjson==3.10.7 + # via langgraph-sdk # via langsmith packaging==24.1 - # via altair # via faiss-cpu + # via gunicorn # via huggingface-hub - # via ipykernel # via langchain-core # via marshmallow # via matplotlib + # via mlflow-skinny # via onnxruntime # via pikepdf - # via streamlit + # via sagemaker # via transformers # via unstructured-client # via unstructured-pytesseract -pandas==2.2.2 - # via diff-assistant +pandas==2.2.3 + # via langchain-cohere # via layoutparser # via llama-index-legacy # via llama-index-readers-file - # via streamlit -parso==0.8.4 - # via jedi + # via mlflow + # via sagemaker + # via unstructured +parameterized==0.9.0 + # via cohere +pathos==0.3.3 + # via sagemaker pdf2docx==0.5.8 # via megaparse pdf2image==1.17.0 @@ -445,13 +535,11 @@ pdfminer-six==20231228 pdfplumber==0.11.4 # via layoutparser # via megaparse -pexpect==4.9.0 - # via ipython pi-heif==0.18.0 # via unstructured -pikepdf==9.2.1 +pikepdf==9.3.0 # via unstructured -pillow==10.4.0 +pillow==11.0.0 # via layoutparser # via llama-index-core # via matplotlib @@ -459,105 +547,102 @@ pillow==10.4.0 # via pdfplumber # via pi-heif # via pikepdf - # via python-doctr # via python-pptx - # via streamlit # via torchvision # via unstructured-pytesseract -platformdirs==4.2.2 - # via jupyter-core +platformdirs==4.3.6 + # via sagemaker + # via sagemaker-core portalocker==2.10.1 # via iopath -prompt-toolkit==3.0.47 - # via ipython +pox==0.3.5 + # via pathos +ppft==1.7.6.9 + # via pathos +propcache==0.2.0 + # via yarl proto-plus==1.24.0 # via google-api-core # via google-cloud-vision -protobuf==5.27.3 +protobuf==4.25.5 # via google-api-core # via google-cloud-vision # via googleapis-common-protos # via grpcio-status + # via mlflow-skinny # via onnx # via onnxruntime # via proto-plus - # via streamlit + # via sagemaker + # via transformers psutil==6.0.0 - # via ipykernel + # via sagemaker # via unstructured -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data pyarrow==17.0.0 - # via streamlit + # via mlflow pyasn1==0.6.1 # via pyasn1-modules # via rsa pyasn1-modules==0.4.1 # via google-auth -pyclipper==1.3.0.post5 - # via python-doctr pycocotools==2.0.8 # via effdet pycparser==2.22 # via cffi -pycryptodome==3.20.0 +pycryptodome==3.21.0 # via megaparse -pydantic==2.8.2 +pydantic==2.9.2 + # via anthropic + # via cohere # via langchain # via langchain-core # via langsmith # via llama-cloud # via llama-index-core # via openai -pydantic-core==2.20.1 + # via quivr-core + # via sagemaker-core + # via sqlmodel +pydantic-core==2.23.4 + # via cohere # via pydantic -pydeck==0.9.1 - # via streamlit pygments==2.18.0 - # via ipython # via rich -pymupdf==1.24.10 +pymupdf==1.24.11 # via pdf2docx -pymupdfb==1.24.10 - # via pymupdf -pyparsing==3.1.2 +pypandoc==1.14 + # via unstructured +pyparsing==3.2.0 # via matplotlib pypdf==4.3.1 - # via diff-assistant # via llama-index-readers-file # via unstructured # via unstructured-client pypdfium2==4.30.0 - # via diff-assistant # via pdfplumber - # via python-doctr python-dateutil==2.9.0.post0 - # via jupyter-client + # via botocore # via matplotlib # via pandas # via unstructured-client -python-doctr==0.9.0 - # via diff-assistant python-docx==1.1.2 # via megaparse # via pdf2docx + # via unstructured python-dotenv==1.0.1 - # via diff-assistant # via megaparse python-iso639==2024.4.27 # via unstructured python-magic==0.4.27 - # via diff-assistant # via unstructured -python-multipart==0.0.9 +python-multipart==0.0.12 # via unstructured-inference python-oxmsg==0.0.1 # via unstructured python-pptx==1.0.2 # via megaparse -pytz==2024.1 + # via unstructured +pytz==2024.2 # via pandas pyyaml==6.0.2 # via huggingface-hub @@ -566,24 +651,27 @@ pyyaml==6.0.2 # via langchain-core # via layoutparser # via llama-index-core + # via mlflow-skinny # via omegaconf + # via sagemaker + # via sagemaker-core # via timm # via transformers -pyzmq==26.1.1 - # via ipykernel - # via jupyter-client -rapidfuzz==3.9.6 - # via python-doctr +quivr-core==0.0.18 +rapidfuzz==3.10.0 # via unstructured # via unstructured-inference referencing==0.35.1 # via jsonschema # via jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via nltk # via tiktoken # via transformers requests==2.32.3 + # via cohere + # via databricks-sdk + # via docker # via google-api-core # via huggingface-hub # via langchain @@ -591,134 +679,147 @@ requests==2.32.3 # via langsmith # via llama-index-core # via llama-index-legacy + # via mlflow-skinny # via requests-toolbelt - # via streamlit + # via sagemaker # via tiktoken # via transformers # via unstructured # via unstructured-client requests-toolbelt==1.0.0 + # via langsmith # via unstructured-client -rich==13.8.0 - # via streamlit +rich==13.9.2 + # via quivr-core + # via sagemaker-core rpds-py==0.20.0 # via jsonschema # via referencing rsa==4.9 # via google-auth +s3transfer==0.10.3 + # via boto3 safetensors==0.4.5 # via timm # via transformers -scikit-learn==1.5.1 - # via diff-assistant +sagemaker==2.232.2 + # via cohere +sagemaker-core==1.0.10 + # via sagemaker +sagemaker-mlflow==0.1.0 + # via sagemaker +schema==0.7.7 + # via sagemaker +scikit-learn==1.5.2 + # via mlflow scipy==1.14.1 # via layoutparser - # via python-doctr + # via mlflow # via scikit-learn -shapely==2.0.6 - # via python-doctr +sentencepiece==0.2.0 + # via transformers six==1.16.0 - # via asttokens - # via fire + # via google-pasta # via langdetect # via python-dateutil # via unstructured-client +smdebug-rulesconfig==1.0.1 + # via sagemaker smmap==5.0.1 # via gitdb sniffio==1.3.1 + # via anthropic # via anyio # via httpx # via openai soupsieve==2.6 # via beautifulsoup4 -sqlalchemy==2.0.32 +sqlalchemy==2.0.36 + # via alembic # via langchain # via langchain-community # via llama-index-core # via llama-index-legacy -stack-data==0.6.3 - # via ipython -streamlit==1.38.0 - # via diff-assistant + # via mlflow + # via sqlmodel +sqlmodel==0.0.22 +sqlparse==0.5.1 + # via mlflow-skinny striprtf==0.0.26 # via llama-index-readers-file -sympy==1.13.2 +sympy==1.13.3 # via onnxruntime # via torch tabulate==0.9.0 + # via langchain-cohere # via unstructured +tblib==3.0.0 + # via sagemaker tenacity==8.5.0 # via langchain # via langchain-community # via langchain-core # via llama-index-core # via llama-index-legacy - # via streamlit -termcolor==2.4.0 +termcolor==2.5.0 # via fire threadpoolctl==3.5.0 # via scikit-learn -tiktoken==0.7.0 +tiktoken==0.8.0 # via langchain-openai # via llama-index-core # via llama-index-legacy -timm==1.0.9 + # via quivr-core +timm==1.0.11 # via effdet # via unstructured-inference -tokenizers==0.19.1 +tokenizers==0.20.1 + # via anthropic + # via cohere # via transformers -toml==0.10.2 - # via streamlit -torch==2.3.1 - # via diff-assistant +torch==2.4.1 # via effdet - # via python-doctr # via timm # via torchvision # via unstructured-inference -torchvision==0.18.1 +torchvision==0.19.1 # via effdet - # via python-doctr # via timm -tornado==6.4.1 - # via ipykernel - # via jupyter-client - # via streamlit tqdm==4.66.5 # via huggingface-hub # via iopath # via llama-index-core # via nltk # via openai - # via python-doctr + # via sagemaker # via transformers # via unstructured -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline -transformers==4.44.2 +transformers==4.45.2 + # via quivr-core # via unstructured-inference +triton==3.0.0 + # via torch +types-pyyaml==6.0.12.20240917 + # via quivr-core +types-requests==2.32.0.20241016 + # via cohere typing-extensions==4.12.2 - # via altair - # via emoji + # via alembic + # via anthropic + # via cohere # via huggingface-hub # via iopath - # via ipython # via langchain-core # via llama-index-core # via llama-index-legacy # via openai + # via opentelemetry-sdk # via pydantic # via pydantic-core # via python-docx # via python-oxmsg # via python-pptx # via sqlalchemy - # via streamlit # via torch # via typing-inspect # via unstructured @@ -728,27 +829,35 @@ typing-inspect==0.9.0 # via llama-index-core # via llama-index-legacy # via unstructured-client -tzdata==2024.1 +tzdata==2024.2 # via pandas -unstructured==0.15.9 - # via diff-assistant +unstructured==0.15.14 # via megaparse -unstructured-client==0.25.5 + # via quivr-core +unstructured-client==0.25.9 # via unstructured unstructured-inference==0.7.36 # via unstructured unstructured-pytesseract==0.3.13 # via unstructured -urllib3==2.2.2 +urllib3==2.2.3 + # via botocore + # via docker # via requests + # via sagemaker + # via types-requests # via unstructured-client -wcwidth==0.2.13 - # via prompt-toolkit +werkzeug==3.0.4 + # via flask wrapt==1.16.0 # via deprecated # via llama-index-core # via unstructured +xlrd==2.0.1 + # via unstructured xlsxwriter==3.2.0 # via python-pptx -yarl==1.9.7 +yarl==1.15.4 # via aiohttp +zipp==3.20.2 + # via importlib-metadata diff --git a/backend/core/examples/simple_question.py b/examples/simple_question/simple_question.py similarity index 66% rename from backend/core/examples/simple_question.py rename to examples/simple_question/simple_question.py index 35ffe1d82..33537d451 100644 --- a/backend/core/examples/simple_question.py +++ b/examples/simple_question/simple_question.py @@ -1,7 +1,6 @@ import tempfile from quivr_core import Brain -from quivr_core.quivr_rag_langgraph import QuivrQARAGLangGraph if __name__ == "__main__": with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as temp_file: @@ -14,6 +13,6 @@ if __name__ == "__main__": ) answer = brain.ask( - "what is gold? asnwer in french", rag_pipeline=QuivrQARAGLangGraph + "what is gold? asnwer in french" ) - print("answer QuivrQARAGLangGraph :", answer.answer) + print("answer QuivrQARAGLangGraph :", answer) diff --git a/backend/core/examples/simple_question_streaming.py b/examples/simple_question_streaming.py similarity index 100% rename from backend/core/examples/simple_question_streaming.py rename to examples/simple_question_streaming.py diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100644 index 574961888..000000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,26 +0,0 @@ -**/.next/ -**/node_modules/ -**/.vercel/ -**/__pycache__ -*/.pytest_cache -**/__pycache__ -**/.benchmarks/ -**/.cache/ -**/.pytest_cache/ -**/.next/ -**/build/ -**/.docusaurus/ - -node_modules -npm-debug.log -Dockerfile* -docker-compose* -.dockerignore -.git -.gitignore -README.md -LICENSE -.vscode -.next -*.swp -/scripts \ No newline at end of file diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index f4051eba8..000000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,177 +0,0 @@ -/* eslint-disable max-lines */ -module.exports = { - plugins: ["prefer-arrow", "import"], - extends: [ - "next", - "next/core-web-vitals", - "eslint:recommended", - "plugin:import/recommended", - ], - ignorePatterns: ["**/node_modules/", "**/.next/"], - rules: { - "import/extensions": 0, - "import/no-unresolved": 0, - "import/prefer-default-export": 0, - "import/no-duplicates": "error", - complexity: ["error", 12], - "max-lines": ["error", 300], - "max-depth": ["error", 3], - "max-params": ["error", 5], - eqeqeq: ["error", "smart"], - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - }, - ], - "no-shadow": [ - "error", - { - hoist: "all", - }, - ], - "prefer-const": "error", - "import/order": [ - "error", - { - pathGroups: [{ pattern: "@lib/**", group: "unknown" }], - groups: [ - ["external", "builtin"], - "unknown", - "internal", - "sibling", - "parent", - "index", - ], - alphabetize: { - order: "asc", - caseInsensitive: false, - }, - "newlines-between": "always", - pathGroupsExcludedImportTypes: ["builtin"], - }, - ], - "import/namespace": "off", - "sort-imports": [ - "error", - { - ignoreCase: true, - ignoreDeclarationSort: true, - ignoreMemberSort: false, - memberSyntaxSortOrder: ["none", "all", "multiple", "single"], - }, - ], - "padding-line-between-statements": [ - "error", - { - blankLine: "always", - prev: "*", - next: "return", - }, - ], - "prefer-arrow/prefer-arrow-functions": [ - "error", - { - disallowPrototype: true, - singleReturnOnly: false, - classPropertiesAllowed: false, - }, - ], - "no-restricted-imports": [ - "error", - { - paths: [ - { - name: "lodash", - message: "Please use lodash/{module} import instead", - }, - { - name: "aws-sdk", - message: "Please use aws-sdk/{module} import instead", - }, - { - name: ".", - message: "Please use explicit import file", - }, - ], - }, - ], - curly: ["error", "all"], - }, - root: true, - env: { - es6: true, - node: true, - browser: true, - }, - parserOptions: { - ecmaVersion: 2021, - sourceType: "module", - babelOptions: { - presets: [require.resolve("next/babel")], - }, - }, - overrides: [ - { - files: ["**/*.ts?(x)"], - extends: [ - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:import/typescript", - ], - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.eslint.json", - tsconfigRootDir: __dirname, - sourceType: "module", - }, - rules: { - "@typescript-eslint/prefer-optional-chain": "error", - "react-hooks/exhaustive-deps": "off", - "no-shadow": "off", - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/prefer-nullish-coalescing": "error", - "@typescript-eslint/ban-ts-comment": [ - "error", - { - "ts-ignore": "allow-with-description", - minimumDescriptionLength: 10, - }, - ], - "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/explicit-member-accessibility": 0, - "@typescript-eslint/camelcase": 0, - "@typescript-eslint/interface-name-prefix": 0, - "@typescript-eslint/explicit-module-boundary-types": "error", - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-unused-vars": "error", - "@typescript-eslint/ban-types": [ - "error", - { - types: { - FC: "Use `const MyComponent = (props: Props): JSX.Element` instead", - SFC: "Use `const MyComponent = (props: Props): JSX.Element` instead", - FunctionComponent: - "Use `const MyComponent = (props: Props): JSX.Element` instead", - "React.FC": - "Use `const MyComponent = (props: Props): JSX.Element` instead", - "React.SFC": - "Use `const MyComponent = (props: Props): JSX.Element` instead", - "React.FunctionComponent": - "Use `const MyComponent = (props: Props): JSX.Element` instead", - }, - extendDefaults: true, - }, - ], - "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", - "@typescript-eslint/no-unnecessary-condition": "error", - "@typescript-eslint/no-unnecessary-type-arguments": "error", - "@typescript-eslint/prefer-string-starts-ends-with": "error", - "@typescript-eslint/switch-exhaustiveness-check": "error", - "@typescript-eslint/restrict-template-expressions": "off", - }, - }, - ], -}; diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index a63b84b2d..000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# Sentry Auth Token -.sentryclirc -/test-results/ -/playwright-report/ -/playwright/.cache/ - -# Sentry Config File -.sentryclirc - -# Sentry Config File -.sentryclirc - -# Sentry Config File -.env.sentry-build-plugin diff --git a/frontend/.lintstagedrc.js b/frontend/.lintstagedrc.js deleted file mode 100644 index 268d825d6..000000000 --- a/frontend/.lintstagedrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - "*": "prettier --ignore-unknown --write", - "*.{js,ts, tsx}": "pnpm lint-fix", -}; diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index ee6bf86d1..000000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,110 +0,0 @@ -FROM node:18.19.0-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat python3 make g++ -RUN yarn global add node-gyp -WORKDIR /app - -# Install dependencies based on the preferred package manager -COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ -RUN \ - if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm ci; \ - elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ - else echo "Lockfile not found." && exit 1; \ - fi - - -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -ARG NEXT_PUBLIC_ENV -ARG NEXT_PUBLIC_BACKEND_URL -ARG NEXT_PUBLIC_SUPABASE_URL -ARG NEXT_PUBLIC_SUPABASE_ANON_KEY -ARG NEXT_PUBLIC_CMS_URL -ARG NEXT_PUBLIC_FRONTEND_URL -ARG NEXT_PUBLIC_AUTH_MODES # Make sure this is declared here if it influences the build -ARG NEXT_PUBLIC_SHOW_TOKENS -ARG NEXT_PUBLIC_PROJECT_NAME -ARG NEXT_PUBLIC_POSTHOG_KEY -ARG NEXT_PUBLIC_POSTHOG_HOST -ARG SENTRY_AUTH_TOKEN -ARG NEXT_PUBLIC_INTERCOM_APP_ID -ENV NEXT_PUBLIC_ENV=$NEXT_PUBLIC_ENV -ENV NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL -ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL -ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY -ENV NEXT_PUBLIC_CMS_URL=$NEXT_PUBLIC_CMS_URL -ENV NEXT_PUBLIC_FRONTEND_URL=$NEXT_PUBLIC_FRONTEND_URL -ENV NEXT_PUBLIC_AUTH_MODES=$NEXT_PUBLIC_AUTH_MODES -ENV NEXT_PUBLIC_SHOW_TOKENS=$NEXT_PUBLIC_SHOW_TOKENS -ENV NEXT_PUBLIC_PROJECT_NAME=$NEXT_PUBLIC_PROJECT_NAME -ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY -ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST -ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN -ENV NEXT_PUBLIC_INTERCOM_APP_ID=$NEXT_PUBLIC_INTERCOM_APP_ID - -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -# ENV NEXT_TELEMETRY_DISABLED 1 - -RUN yarn build - -# If using npm comment out above and use below instead -# RUN npm run build - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV production -# Uncomment the following line in case you want to disable telemetry during runtime. -# ENV NEXT_TELEMETRY_DISABLED 1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public - -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 3000 - -ENV PORT 3000 -# set hostname to localhost -ENV HOSTNAME "0.0.0.0" - -ARG NEXT_PUBLIC_AUTH_MODES -ENV NEXT_PUBLIC_AUTH_MODES=$NEXT_PUBLIC_AUTH_MODES -ARG NEXT_PUBLIC_SHOW_TOKENS -ENV NEXT_PUBLIC_SHOW_TOKENS=$NEXT_PUBLIC_SHOW_TOKENS -ARG NEXT_PUBLIC_PROJECT_NAME -ENV NEXT_PUBLIC_PROJECT_NAME=$NEXT_PUBLIC_PROJECT_NAME -ARG NEXT_PUBLIC_POSTHOG_KEY -ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY -ARG NEXT_PUBLIC_POSTHOG_HOST -ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST -ARG SENTRY_AUTH_TOKEN -ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN -ARG NEXT_PUBLIC_INTERCOM_APP_ID -ENV NEXT_PUBLIC_INTERCOM_APP_ID=$NEXT_PUBLIC_INTERCOM_APP_ID - -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["node", "server.js"] \ No newline at end of file diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev deleted file mode 100644 index e096f5ae3..000000000 --- a/frontend/Dockerfile.dev +++ /dev/null @@ -1,28 +0,0 @@ -FROM node:18.13.0-alpine -# Install Python and essential build tools -RUN apk add --update --no-cache python3 make g++ && ln -sf python3 /usr/bin/python -RUN python3 -m ensurepip -RUN pip3 install --no-cache --upgrade pip setuptools - -# Create the directory on the node image -# where our Next.js app will live -RUN mkdir -p /app - -# Set /app as the working directory -WORKDIR /app - -# Copy package.json and yarn.lock -# to the /app working directory -COPY package*.json yarn.lock ./ - -# Install dependencies in /app -RUN yarn install --network-timeout 1000000 - -# Copy the rest of our Next.js folder into /app -COPY . . - -# Ensure port 3000 is accessible to our system -EXPOSE 3000 - -# Run yarn dev, as we would via the command line -CMD ["yarn", "dev"] diff --git a/frontend/Porter.yaml b/frontend/Porter.yaml deleted file mode 100644 index 0343c17c6..000000000 --- a/frontend/Porter.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: v2 -name: quivr-demo-front -services: - - name: quivr-frontend - run: "" - type: web - instances: 1 - cpuCores: 0.2 - ramMegabytes: 240 - terminationGracePeriodSeconds: 30 - port: 3000 - domains: - - name: demo.quivr.app - sleep: false -build: - context: ./frontend - method: docker - dockerfile: ./frontend/Dockerfile -envGroups: - - preview-frontend -autoRollback: - enabled: false diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index a6b523f39..000000000 --- a/frontend/README.md +++ /dev/null @@ -1,34 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/frontend/app/(auth)/login/components/AzureLogin/hooks/useAzureLogin.ts b/frontend/app/(auth)/login/components/AzureLogin/hooks/useAzureLogin.ts deleted file mode 100644 index fddddeb58..000000000 --- a/frontend/app/(auth)/login/components/AzureLogin/hooks/useAzureLogin.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from "react"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks/useToast"; - -export const useAzureLogin = (): { - signInWithAzure: () => Promise; - isPending: boolean; -} => { - const { supabase } = useSupabase(); - const { publish } = useToast(); - const [isPending, setIsPending] = useState(false); - - const signInWithAzure = async () => { - setIsPending(true); - const { error } = await supabase.auth.signInWithOAuth({ - provider: "azure", - options: { - scopes: "email", - }, - }); - setIsPending(false); - if (error) { - publish({ - variant: "danger", - text: "An error occurred during Azure login", - }); - } - }; - - return { - signInWithAzure, - isPending, - }; -}; diff --git a/frontend/app/(auth)/login/components/AzureLogin/index.tsx b/frontend/app/(auth)/login/components/AzureLogin/index.tsx deleted file mode 100644 index 92babffff..000000000 --- a/frontend/app/(auth)/login/components/AzureLogin/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { SiMicrosoftazure } from "react-icons/si"; - -import Button from "@/lib/components/ui/Button"; - -import { useAzureLogin } from "./hooks/useAzureLogin"; - -export const AzureLoginButton = (): JSX.Element => { - const { isPending, signInWithAzure } = useAzureLogin(); - const { t } = useTranslation(["login"]); - - return ( - - ); -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/components/EmailInput.tsx b/frontend/app/(auth)/login/components/EmailLogin/components/EmailInput.tsx deleted file mode 100644 index 2d71f0c15..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/components/EmailInput.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Fragment } from "react"; -import { Controller } from "react-hook-form"; - -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { useAuthModes } from "@/lib/hooks/useAuthModes"; - -export const EmailInput = (): JSX.Element => { - const { password, magicLink } = useAuthModes(); - if (!password && !magicLink) { - return ; - } - - return ( - ( - - )} - /> - ); -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/MaginLinkLogin.tsx b/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/MaginLinkLogin.tsx deleted file mode 100644 index fd8dfaee4..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/MaginLinkLogin.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Fragment } from "react"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { EmailAuthContextType } from "@/app/(auth)/login/types"; -import Button from "@/lib/components/ui/Button"; -import { useAuthModes } from "@/lib/hooks/useAuthModes"; - -import { useMagicLinkLogin } from "./hooks/useMagicLinkLogin"; - -export const MagicLinkLogin = (): JSX.Element => { - const { t } = useTranslation(["login", "translation"]); - const { magicLink } = useAuthModes(); - const { handleMagicLinkLogin } = useMagicLinkLogin(); - const { watch } = useFormContext(); - - if (!magicLink) { - return ; - } - - return ( - - ); -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/hooks/useMagicLinkLogin.ts b/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/hooks/useMagicLinkLogin.ts deleted file mode 100644 index 5a1ed5846..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/components/MagicLinkLogin/hooks/useMagicLinkLogin.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { EmailAuthContextType } from "@/app/(auth)/login/types"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useMagicLinkLogin = () => { - const { supabase } = useSupabase(); - const { watch, setValue } = useFormContext(); - - const { t } = useTranslation("login"); - const { publish } = useToast(); - - const email = watch("email"); - - const handleMagicLinkLogin = async () => { - if (email === "") { - publish({ - variant: "danger", - text: t("errorMailMissed"), - }); - - return; - } - setValue("isMagicLinkSubmitting", true); - const { error } = await supabase.auth.signInWithOtp({ - email, - options: { - emailRedirectTo: window.location.hostname, - }, - }); - setValue("isMagicLinkSubmitting", false); - setValue("isMagicLinkSubmitted", true); - - if (error) { - publish({ - variant: "danger", - text: error.message, - }); - - throw error; - } - }; - - return { - handleMagicLinkLogin, - }; -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/PasswordLogin.tsx b/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/PasswordLogin.tsx deleted file mode 100644 index a0f5ee514..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/PasswordLogin.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Fragment } from "react"; -import { Controller, useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { EmailAuthContextType } from "@/app/(auth)/login/types"; -import Button from "@/lib/components/ui/Button"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { useAuthModes } from "@/lib/hooks/useAuthModes"; - -import { usePasswordLogin } from "./hooks/usePasswordLogin"; - -export const PasswordLogin = (): JSX.Element => { - const { t } = useTranslation(["login"]); - const { password } = useAuthModes(); - const { handlePasswordLogin } = usePasswordLogin(); - const { watch } = useFormContext(); - - if (!password) { - return ; - } - - return ( -
- ( - - )} - /> - -
- ); -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/hooks/usePasswordLogin.ts b/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/hooks/usePasswordLogin.ts deleted file mode 100644 index 9edb9d16e..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/components/PasswordLogin/hooks/usePasswordLogin.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { EmailAuthContextType } from "@/app/(auth)/login/types"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePasswordLogin = () => { - const { supabase } = useSupabase(); - const { t } = useTranslation("login"); - const { publish } = useToast(); - const { watch, setValue } = useFormContext(); - - const email = watch("email"); - const password = watch("password"); - - const handlePasswordLogin = async () => { - if (email === "") { - publish({ - variant: "danger", - text: t("errorMailMissed"), - }); - - return; - } - - if (password === "") { - publish({ - variant: "danger", - text: t("errorPasswordMissed"), - }); - - return; - } - setValue("isPasswordSubmitting", true); - const { error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - setValue("isPasswordSubmitting", false); - setValue("isPasswordSubmitted", true); - - if (error) { - publish({ - variant: "danger", - text: error.message, - }); - - throw error; // this error is caught by react-hook-form - } - }; - - return { - handlePasswordLogin, - }; -}; diff --git a/frontend/app/(auth)/login/components/EmailLogin/index.tsx b/frontend/app/(auth)/login/components/EmailLogin/index.tsx deleted file mode 100644 index 30264d20f..000000000 --- a/frontend/app/(auth)/login/components/EmailLogin/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { Divider } from "@/lib/components/ui/Divider"; -import { useAuthModes } from "@/lib/hooks/useAuthModes"; - -import { EmailInput } from "./components/EmailInput"; -import { MagicLinkLogin } from "./components/MagicLinkLogin/MaginLinkLogin"; -import { PasswordLogin } from "./components/PasswordLogin/PasswordLogin"; - -import { EmailAuthContextType } from "../../types"; - -export const EmailLogin = (): JSX.Element => { - const { reset } = useFormContext(); - const { watch } = useFormContext(); - - const { t } = useTranslation(["login", "translation"]); - const { password, magicLink } = useAuthModes(); - - if (watch("isMagicLinkSubmitted")) { - return ( -
-

- {t("check_your_email.part1", { ns: "login" })}{" "} - - {t("check_your_email.magic_link", { ns: "login" })} - {" "} - {t("check_your_email.part2", { ns: "login" })} -

-
- {t("cant_find", { ns: "login" })}{" "} - void reset()} - > - {t("try_again")} - -
-
- ); - } - - return ( - <> - - - {password && magicLink && ( - - )} - - - ); -}; diff --git a/frontend/app/(auth)/login/components/GoogleLogin/hooks/__tests__/useGoogleLogin.test.ts b/frontend/app/(auth)/login/components/GoogleLogin/hooks/__tests__/useGoogleLogin.test.ts deleted file mode 100644 index 269e9c73b..000000000 --- a/frontend/app/(auth)/login/components/GoogleLogin/hooks/__tests__/useGoogleLogin.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { renderHook } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { useGoogleLogin } from "../useGoogleLogin"; - -const mockSignInWithOAuth = vi.fn(() => ({ error: null })); - -const mockUseSupabase = () => ({ - supabase: { - auth: { - signInWithOAuth: mockSignInWithOAuth, - }, - }, -}); - -vi.mock("@/lib/context/SupabaseProvider", () => ({ - useSupabase: () => mockUseSupabase(), -})); - -describe("useGoogleLogin", () => { - it("should call signInWithOAuth", async () => { - const { result } = renderHook(() => useGoogleLogin()); - - await result.current.signInWithGoogle(); - - expect(mockSignInWithOAuth).toHaveBeenCalledTimes(1); - }); -}); diff --git a/frontend/app/(auth)/login/components/GoogleLogin/hooks/useGoogleLogin.ts b/frontend/app/(auth)/login/components/GoogleLogin/hooks/useGoogleLogin.ts deleted file mode 100644 index 1fec58595..000000000 --- a/frontend/app/(auth)/login/components/GoogleLogin/hooks/useGoogleLogin.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useState } from "react"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks/useToast"; - -export const useGoogleLogin = (): { - signInWithGoogle: () => Promise; - isPending: boolean; -} => { - const { supabase } = useSupabase(); - - const { publish } = useToast(); - - const [isPending, setIsPending] = useState(false); - - const signInWithGoogle = async () => { - setIsPending(true); - const { error } = await supabase.auth.signInWithOAuth({ - provider: "google", - options: { - queryParams: { - access_type: "offline", - prompt: "consent", - }, - }, - }); - setIsPending(false); - if (error) { - publish({ - variant: "danger", - text: "An error occurred ", - }); - } - }; - - return { - signInWithGoogle, - isPending, - }; -}; diff --git a/frontend/app/(auth)/login/components/GoogleLogin/index.tsx b/frontend/app/(auth)/login/components/GoogleLogin/index.tsx deleted file mode 100644 index 497802b37..000000000 --- a/frontend/app/(auth)/login/components/GoogleLogin/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { FcGoogle } from "react-icons/fc"; - -import Button from "@/lib/components/ui/Button"; - -import { useGoogleLogin } from "./hooks/useGoogleLogin"; - -export const GoogleLoginButton = (): JSX.Element => { - const { isPending, signInWithGoogle } = useGoogleLogin(); - const { t } = useTranslation(["login"]); - - return ( - - ); -}; diff --git a/frontend/app/(auth)/login/hooks/useLogin.ts b/frontend/app/(auth)/login/hooks/useLogin.ts deleted file mode 100644 index afdeedb4c..000000000 --- a/frontend/app/(auth)/login/hooks/useLogin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect } from "react"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { redirectToPreviousPageOrSearchPage } from "@/lib/helpers/redirectToPreviousPageOrSearchPage"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useLogin = () => { - const { session } = useSupabase(); - - const { track } = useEventTracking(); - - useEffect(() => { - if (session?.user !== undefined) { - void track("SIGNED_IN"); - redirectToPreviousPageOrSearchPage(); - } - }, [session?.user]); -}; diff --git a/frontend/app/(auth)/login/page.module.scss b/frontend/app/(auth)/login/page.module.scss deleted file mode 100644 index abc534438..000000000 --- a/frontend/app/(auth)/login/page.module.scss +++ /dev/null @@ -1,46 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.login_page_wrapper { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - - .section { - max-width: 500px; - display: flex; - flex-direction: column; - row-gap: Spacings.$spacing03; - - .logo_link { - display: flex; - justify-content: center; - } - - .title { - @include Typography.Big; - text-align: center; - - .primary_text { - color: var(--primary); - } - } - - .form_container { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - } - - .divider { - text-transform: uppercase; - } - - .restriction_message { - text-align: center; - font-size: Typography.$tiny; - } - } -} diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx deleted file mode 100644 index 7f0e02cb5..000000000 --- a/frontend/app/(auth)/login/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; -import Link from "next/link"; -import { Suspense } from "react"; -import { FormProvider, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { QuivrLogo } from "@/lib/assets/QuivrLogo"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useAuthModes } from "@/lib/hooks/useAuthModes"; - -import { AzureLoginButton } from "./components/AzureLogin"; -import { EmailLogin } from "./components/EmailLogin"; -import { GoogleLoginButton } from "./components/GoogleLogin"; -import { useLogin } from "./hooks/useLogin"; -import styles from "./page.module.scss"; -import { EmailAuthContextType } from "./types"; - -const projectName = process.env.NEXT_PUBLIC_PROJECT_NAME; - -const Main = (): JSX.Element => { - useLogin(); - const { googleSso, azureSso } = useAuthModes(); - const { isDarkMode } = useUserSettingsContext(); - - const methods = useForm({ - defaultValues: { - email: "", - password: "", - }, - }); - const { t } = useTranslation(["translation", "login"]); - - return ( -
-
- - - -

- {t("talk_to", { ns: "login" })}{" "} - - {projectName ? projectName : "Quivr"} - -

-
- - - - - {googleSso && } - {azureSso && } -
-
-
- ); -}; - -const Login = (): JSX.Element => { - return ( - -
- - ); -}; - -export default Login; diff --git a/frontend/app/(auth)/login/types.ts b/frontend/app/(auth)/login/types.ts deleted file mode 100644 index 8edb5f81f..000000000 --- a/frontend/app/(auth)/login/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type EmailAuthContextType = { - email: string; - password: string; - isMagicLinkSubmitted: boolean; - isPasswordSubmitted: boolean; - isMagicLinkSubmitting: boolean; - isPasswordSubmitting: boolean; -}; diff --git a/frontend/app/(home)/page.tsx b/frontend/app/(home)/page.tsx deleted file mode 100644 index cbcdaff4d..000000000 --- a/frontend/app/(home)/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; -import { Suspense } from "react"; - -import Login from "../(auth)/login/page"; - -const Main = (): JSX.Element => { - return ; -}; - -const Home = (): JSX.Element => { - return ( - -
- - ); -}; - -export default Home; diff --git a/frontend/app/App.module.scss b/frontend/app/App.module.scss deleted file mode 100644 index d44ef1fbb..000000000 --- a/frontend/app/App.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.app_container { - display: flex; - overflow: auto; - position: relative; - align-items: stretch; - width: 100%; - height: 100%; - - @media (max-width: ScreenSizes.$small) { - .menu_container { - position: absolute; - left: 0; - top: 0; - z-index: ZIndexes.$overlay; - } - } - - &.blur { - filter: opacity(0.1); - transition: filter 0.2s ease-in-out; - } - - .content_container { - overflow: auto; - flex: 1 1 0%; - - @media (max-width: ScreenSizes.$small) { - &.blured { - opacity: 0.2; - } - } - } -} diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx deleted file mode 100644 index 401b3258a..000000000 --- a/frontend/app/App.tsx +++ /dev/null @@ -1,133 +0,0 @@ -"use client"; - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { posthog } from "posthog-js"; -import { PostHogProvider } from "posthog-js/react"; -import { PropsWithChildren, useEffect } from "react"; - -import { BrainCreationProvider } from "@/lib/components/AddBrainModal/brainCreation-provider"; -import { HelpWindow } from "@/lib/components/HelpWindow/HelpWindow"; -import { Menu } from "@/lib/components/Menu/Menu"; -import { useOutsideClickListener } from "@/lib/components/Menu/hooks/useOutsideClickListener"; -import { SearchModal } from "@/lib/components/SearchModal/SearchModal"; -import { - BrainProvider, - ChatProvider, - KnowledgeToFeedProvider, -} from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { ChatsProvider } from "@/lib/context/ChatsProvider"; -import { HelpProvider } from "@/lib/context/HelpProvider/help-provider"; -import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext"; -import { MenuProvider } from "@/lib/context/MenuProvider/Menu-provider"; -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { NotificationsProvider } from "@/lib/context/NotificationsProvider/notifications-provider"; -import { OnboardingProvider } from "@/lib/context/OnboardingProvider/Onboarding-provider"; -import { SearchModalProvider } from "@/lib/context/SearchModalProvider/search-modal-provider"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { UserSettingsProvider } from "@/lib/context/UserSettingsProvider/User-settings.provider"; -import { IntercomProvider } from "@/lib/helpers/intercom/IntercomProvider"; -import { UpdateMetadata } from "@/lib/helpers/updateMetadata"; -import { usePageTracking } from "@/services/analytics/june/usePageTracking"; - -import "../lib/config/LocaleConfig/i18n"; -import styles from "./App.module.scss"; -import { FromConnectionsProvider } from "./chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/FromConnection-provider"; - -if ( - process.env.NEXT_PUBLIC_POSTHOG_KEY != null && - process.env.NEXT_PUBLIC_POSTHOG_HOST != null -) { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { - api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, - opt_in_site_apps: true, - disable_session_recording: true, - }); -} - -// This wrapper is used to make effect calls at a high level in app rendering. -const App = ({ children }: PropsWithChildren): JSX.Element => { - const { fetchAllBrains } = useBrainContext(); - const { onClickOutside } = useOutsideClickListener(); - const { session } = useSupabase(); - const { isOpened } = useMenuContext(); - const { isVisible } = useHelpContext(); - - usePageTracking(); - - useEffect(() => { - if (session?.user) { - void fetchAllBrains(); - - posthog.identify(session.user.id, { email: session.user.email }); - posthog.startSessionRecording(); - } - }, [session]); - - return ( - <> - - -
- - - -
-
- -
-
- {children} -
- -
-
-
-
-
- - ); -}; - -const queryClient = new QueryClient(); - -const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => { - return ( - - - - - - - - - - - - - {children} - - - - - - - - - - - - - ); -}; - -export { AppWithQueryClient as App }; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/ActionsBar.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/ActionsBar.tsx deleted file mode 100644 index 161051694..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/ActionsBar.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { AiOutlineLoading3Quarters } from "react-icons/ai"; - -import { ChatInput } from "./components"; -import { useActionBar } from "./hooks/useActionBar"; - -export const ActionsBar = (): JSX.Element => { - const { hasPendingRequests } = useActionBar(); - - const { t } = useTranslation(["chat"]); - - return ( - <> - {hasPendingRequests && ( -
-
- {t("feedingBrain")} -
- -
- )} - -
- -
- - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/ChatEditor.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/ChatEditor.tsx deleted file mode 100644 index f4971f5c3..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/ChatEditor.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Editor } from "./Editor/Editor"; - -type ChatEditorProps = { - onSubmit: () => void; - setMessage: (text: string) => void; - message: string; -}; -export const ChatEditor = ({ - onSubmit, - setMessage, - message, -}: ChatEditorProps): JSX.Element => ( - -); diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.module.scss deleted file mode 100644 index 1709cf6d4..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.editor_wrapper { - width: 100%; - caret-color: var(--accent); - max-height: 100px; - overflow: auto; -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.tsx deleted file mode 100644 index f5adbc104..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { EditorContent } from "@tiptap/react"; -import { useEffect } from "react"; -import "./styles.css"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import styles from "./Editor.module.scss"; -import { useChatStateUpdater } from "./hooks/useChatStateUpdater"; -import { useCreateEditorState } from "./hooks/useCreateEditorState"; -import { useEditor } from "./hooks/useEditor"; - -type EditorProps = { - onSubmit: () => void; - setMessage: (text: string) => void; - message: string; - placeholder?: string; -}; - -export const Editor = ({ - setMessage, - onSubmit, - placeholder, - message, -}: EditorProps): JSX.Element => { - const { editor } = useCreateEditorState(placeholder); - const { currentBrain } = useBrainContext(); - - useEffect(() => { - const htmlString = editor?.getHTML(); - if ( - message === "" || - (htmlString && - new DOMParser().parseFromString(htmlString, "text/html").body - .textContent === " ") - ) { - editor?.commands.clearContent(); - } - }, [message, editor]); - - useEffect(() => { - editor?.commands.focus(); - }, [currentBrain, editor]); - - useEffect(() => { - if (editor && placeholder) { - ( - editor.extensionManager.extensions.find( - (ext) => ext.name === "placeholder" - ) as { options: { placeholder: string } } - ).options.placeholder = placeholder; - editor.view.updateState(editor.state); - } - }, [placeholder, editor]); - - useChatStateUpdater({ - editor, - setMessage, - }); - - const { submitOnEnter } = useEditor({ - onSubmit, - }); - - return ( - { - if (event.key === "Enter" && !event.shiftKey && !currentBrain) { - event.preventDefault(); - const lastChar = editor?.state.doc.textBetween( - editor.state.selection.$from.pos - 1, - editor.state.selection.$from.pos, - undefined, - "\ufffc" - ); - if (lastChar === " ") { - editor?.chain().insertContent("@").focus().run(); - } else { - editor?.chain().insertContent(" @").focus().run(); - } - } else { - submitOnEnter(event); - } - }} - editor={editor} - /> - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss deleted file mode 100644 index 1b6b98ad9..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.mention_item_wrapper { - display: flex; - cursor: pointer; - gap: Spacings.$spacing03; - align-items: center; - border-radius: Radius.$normal; - overflow: hidden; - padding: Spacings.$spacing02; - min-height: 2rem; - - &:hover { - background-color: var(--background-3); - } - - &.selected { - color: var(--primary-0); - } - - .brain_image { - border-radius: Radius.$normal; - } - - .brain_snippet { - min-width: 18px; - min-height: 18px; - max-width: 18px; - max-height: 18px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$very_tiny; - } - - .brain_name { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.tsx deleted file mode 100644 index e1b9b84d4..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import Image from "next/image"; - -import styles from "./MentionItem.module.scss"; - -import { SuggestionItem } from "../../types"; - -type MentionItemProps = { - item: SuggestionItem; - onClick: () => void; - selected: boolean; -}; - -export const MentionItem = ({ - item, - onClick, - selected, -}: MentionItemProps): JSX.Element => { - return ( - - {item.iconUrl ? ( - Brain or Model - ) : ( -
- {item.snippet_emoji} -
- )} - {item.label} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss deleted file mode 100644 index 5469df2c8..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; - -.mentions_list_wrapper { - display: flex; - background-color: var(--background-0); - flex-direction: column; - padding: Spacings.$spacing03; - box-shadow: BoxShadow.$medium; - border: 1px solid var(--border-1); - border-radius: Radius.$normal; - gap: Spacings.$spacing02; - max-width: 300px; - min-width: 200px; - overflow: auto; - max-height: 300px; - overflow-y: auto; - - .mentions_list { - display: flex; - flex-direction: column; - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.tsx deleted file mode 100644 index 711554e09..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { SuggestionKeyDownProps } from "@tiptap/suggestion"; -import { forwardRef } from "react"; - -import { MentionItem } from "./MentionItem/MentionItem"; -import styles from "./MentionsList.module.scss"; -import { useMentionList } from "./hooks/useMentionList"; -import { MentionListProps } from "./types"; - -export type MentionListRef = { - onKeyDown: (event: SuggestionKeyDownProps) => boolean; -}; - -export const MentionList = forwardRef( - (props, ref) => { - const { selectItem, selectedIndex } = useMentionList({ - ...props, - ref, - }); - - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - - return ( -
-
- {props.suggestionData.items.map((item, index) => ( - selectItem(index)} - selected={selectedIndex === index} - /> - ))} -
-
- ); - } -); - -MentionList.displayName = "MentionList"; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/hooks/useMentionList.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/hooks/useMentionList.ts deleted file mode 100644 index a5842c2eb..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/hooks/useMentionList.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { SuggestionKeyDownProps } from "@tiptap/suggestion"; -import { ForwardedRef, useEffect, useImperativeHandle, useState } from "react"; - -import { MentionListRef } from "../MentionsList"; -import { MentionListProps } from "../types"; - -type UseMentionListProps = MentionListProps & { - ref: ForwardedRef; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useMentionList = (props: UseMentionListProps) => { - const [selectedIndex, setSelectedIndex] = useState(0); - - const selectItem = (index: number) => { - const item = props.suggestionData.items[index]; - - if (item !== undefined) { - props.command(item); - } - }; - - const upHandler = () => { - setSelectedIndex( - (selectedIndex + props.suggestionData.items.length - 1) % - props.suggestionData.items.length - ); - }; - - const downHandler = () => { - setSelectedIndex((selectedIndex + 1) % props.suggestionData.items.length); - }; - - const enterHandler = () => { - selectItem(selectedIndex); - }; - - useEffect(() => setSelectedIndex(0), [props.suggestionData]); - - useImperativeHandle(props.ref, () => ({ - onKeyDown: ({ event }: SuggestionKeyDownProps) => { - const { key } = event; - - if (key === "ArrowUp") { - upHandler(); - - return true; - } - - if (key === "ArrowDown") { - downHandler(); - - return true; - } - - if (key === "Enter" && !event.shiftKey) { - event.preventDefault(); - event.stopPropagation(); - enterHandler(); - - return true; - } - - return false; - }, - })); - - const isBrain = props.suggestionData.type === "brain"; - - const isPrompt = props.suggestionData.type === "prompt"; - - return { - isBrain, - selectedIndex, - selectItem, - isPrompt, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/types.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/types.ts deleted file mode 100644 index b4771240c..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SuggestionData, SuggestionItem } from "../types"; - -export type MentionListProps = { - suggestionData: SuggestionData; - command: (item: SuggestionItem) => void; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useBrainMention.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useBrainMention.ts deleted file mode 100644 index bdee4a392..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useBrainMention.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { useMentionConfig } from "./useMentionConfig"; - -import { SuggestionItem } from "../types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainMention = () => { - const { allBrains } = useBrainContext(); - - const items: SuggestionItem[] = allBrains.map((brain) => ({ - id: brain.id, - label: brain.display_name ?? brain.name, - type: "brain", - iconUrl: brain.image_url, - snippet_emoji: brain.snippet_emoji, - snippet_color: brain.snippet_color, - })); - - const { Mention: BrainMention } = useMentionConfig({ - char: "@", - suggestionData: { - type: "brain", - items, - }, - }); - - return { - BrainMention, - items, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useChatStateUpdater.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useChatStateUpdater.ts deleted file mode 100644 index 7186496ff..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useChatStateUpdater.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Editor, EditorEvents } from "@tiptap/core"; -import { UUID } from "crypto"; -import { useCallback, useEffect } from "react"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { getChatInputAttributesFromEditorState } from "../utils/getChatInputAttributesFromEditorState"; -import { removeExistingMentionFromEditor } from "../utils/removeExistingMentionFromEditor"; - -type UseChatStateUpdaterProps = { - editor: Editor | null; - setMessage: (message: string) => void; -}; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatStateUpdater = ({ - editor, - setMessage, -}: UseChatStateUpdaterProps) => { - const { - currentBrainId, - currentPromptId, - setCurrentBrainId, - setCurrentPromptId, - } = useBrainContext(); - - const onEditorUpdate = useCallback( - ({ editor: editorNewState }: EditorEvents["update"]) => { - const { text, brainId, promptId } = - getChatInputAttributesFromEditorState(editorNewState); - - setMessage(text); - - if (brainId !== currentBrainId) { - if (brainId === "") { - return; - } else { - if (currentBrainId !== null) { - removeExistingMentionFromEditor(editorNewState, "mention@"); - } - setCurrentBrainId(brainId as UUID); - } - } - if (promptId !== currentPromptId) { - if (promptId === "") { - setCurrentPromptId(null); - } else { - if (currentPromptId !== null) { - removeExistingMentionFromEditor(editorNewState, "mention#"); - } - setCurrentPromptId(promptId as UUID); - } - } - }, - [ - currentBrainId, - currentPromptId, - setCurrentBrainId, - setCurrentPromptId, - setMessage, - ] - ); - - useEffect(() => { - editor?.on("update", onEditorUpdate); - - return () => { - editor?.off("update", onEditorUpdate); - }; - }, [editor, onEditorUpdate, setMessage]); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useCreateEditorState.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useCreateEditorState.ts deleted file mode 100644 index d6fac3394..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useCreateEditorState.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Document } from "@tiptap/extension-document"; -import { HardBreak } from "@tiptap/extension-hard-break"; -import { Paragraph } from "@tiptap/extension-paragraph"; -import { Placeholder } from "@tiptap/extension-placeholder"; -import { Text } from "@tiptap/extension-text"; -import { Extension, useEditor } from "@tiptap/react"; -import { useTranslation } from "react-i18next"; - -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import { useBrainMention } from "./useBrainMention"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useCreateEditorState = (placeholder?: string) => { - const { t } = useTranslation(["chat"]); - const { BrainMention, items } = useBrainMention(); - const { remainingCredits } = useUserSettingsContext(); - - const PreventNewline = Extension.create({ - addKeyboardShortcuts: () => { - return { - Enter: () => true, - Escape: () => true, - }; - }, - }); - - const editor = useEditor( - { - autofocus: !!remainingCredits, - onFocus: () => { - editor?.commands.focus("end"); - }, - parseOptions: { - preserveWhitespace: "full", - }, - extensions: [ - PreventNewline, - Placeholder.configure({ - showOnlyWhenEditable: true, - placeholder: placeholder ?? t("actions_bar_placeholder"), - }), - Document, - Text, - Paragraph, - BrainMention, - HardBreak, - ], - }, - [items.length] - ); - - return { - editor, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useEditor.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useEditor.ts deleted file mode 100644 index 3a4b7ac20..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useEditor.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { KeyboardEvent } from "react"; - -type UseEditorProps = { - onSubmit: () => void; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useEditor = ({ onSubmit }: UseEditorProps) => { - const submitOnEnter = (ev: KeyboardEvent) => { - if (ev.key === "Enter" && !ev.shiftKey && !ev.metaKey) { - onSubmit(); - } - }; - - return { - submitOnEnter, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useMentionConfig.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useMentionConfig.ts deleted file mode 100644 index 872e8abcc..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/hooks/useMentionConfig.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { default as TiptapMention } from "@tiptap/extension-mention"; -import { PluginKey } from "@tiptap/pm/state"; -import { ReactRenderer } from "@tiptap/react"; -import { SuggestionOptions } from "@tiptap/suggestion"; -import { RefAttributes, useMemo } from "react"; -import tippy, { Instance } from "tippy.js"; - -import { MentionList, MentionListRef } from "../MentionsList/MentionsList"; -import { MentionListProps } from "../MentionsList/types"; -import { SuggestionData, SuggestionItem } from "../types"; - -type UseMentionConfigProps = { - char: string; - suggestionData: SuggestionData; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useMentionConfig = ({ - char, - suggestionData, -}: UseMentionConfigProps) => { - const mentionKey = `mention${char}`; - const items = suggestionData.items; - - const suggestionsConfig = useMemo< - Omit, "editor"> - >( - () => ({ - char, - allowSpaces: true, - pluginKey: new PluginKey(mentionKey), - items: ({ query }) => - items.filter((item) => - item.label.toLowerCase().startsWith(query.toLowerCase()) - ), - render: () => { - let reactRenderer: - | ReactRenderer< - MentionListRef, - MentionListProps & RefAttributes - > - | undefined; - let popup: Instance[] | undefined; - - return { - onStart: (props) => { - if (!props.clientRect) { - return; - } - reactRenderer = new ReactRenderer(MentionList, { - props: { - ...props, - suggestionData: { - ...suggestionData, - items: props.items, - }, - }, - editor: props.editor, - }); - popup = tippy("body", { - zIndex: 1020, - getReferenceClientRect: () => { - const rect = props.clientRect?.(); - - return rect ? rect : new DOMRect(0, 0, 0, 0); - }, - appendTo: () => document.body, - content: reactRenderer.element, - showOnCreate: true, - interactive: true, - trigger: "manual", - placement: "top-start", - }); - }, - onUpdate: (props) => { - reactRenderer?.updateProps({ - ...props, - suggestionData: { - ...suggestionData, - items: props.items, - }, - }); - }, - onKeyDown: (props) => { - if (props.event.key === "Escape") { - popup?.[0].hide(); - - return true; - } - - return reactRenderer?.ref?.onKeyDown(props) ?? false; - }, - onExit: () => { - popup?.[0].destroy(); - reactRenderer?.destroy(); - }, - }; - }, - }), - [char, items, mentionKey, suggestionData] - ); - - const Mention = TiptapMention.extend({ - name: mentionKey, - }).configure({ - HTMLAttributes: { - class: "mention", - }, - suggestion: suggestionsConfig, - renderLabel: () => { - return ""; - }, - }); - - return { - Mention, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/styles.css b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/styles.css deleted file mode 100644 index e35f0d6c5..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/styles.css +++ /dev/null @@ -1,16 +0,0 @@ -.ProseMirror p.is-editor-empty:first-child::before { - content: attr(data-placeholder); - float: left; - color: #adb5bd; - pointer-events: none; - height: 0; -} - -.ProseMirror * { - white-space: pre-wrap; - word-wrap: break-word; -} - -.mention { - display: none; -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/types.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/types.ts deleted file mode 100644 index 932b1c59e..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type SuggestionDataType = "prompt" | "brain"; - -export type SuggestionItem = { - id: string; - label: string; - iconUrl?: string; - snippet_emoji?: string; - snippet_color?: string; -}; - -export type SuggestionData = { - type: SuggestionDataType; - items: SuggestionItem[]; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/getChatInputAttributesFromEditorState.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/getChatInputAttributesFromEditorState.ts deleted file mode 100644 index 2cf4a2ba0..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/getChatInputAttributesFromEditorState.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Editor } from "@tiptap/core"; - -type ChatInputAttributes = { - text: string; - promptId: string; - brainId: string; -}; - -export const getChatInputAttributesFromEditorState = ( - editor: Editor | null -): ChatInputAttributes => { - if (editor === null) { - return { - text: "", - promptId: "", - brainId: "", - }; - } - - const editorJsonContent = editor.getJSON(); - - if ( - editorJsonContent.content === undefined || - editorJsonContent.content.length === 0 - ) { - return { - text: "", - promptId: "", - brainId: "", - }; - } - - let text = ""; - let prompt = ""; - let brain = ""; - - editorJsonContent.content.forEach((block) => { - if (block.content === undefined || block.content.length === 0) { - return; - } - - block.content.forEach((innerBlock) => { - if (innerBlock.type === "text") { - text += innerBlock.text; - } - if (innerBlock.type === "mention#") { - prompt = (innerBlock.attrs?.id as string | undefined) ?? ""; - } - if (innerBlock.type === "mention@") { - brain = (innerBlock.attrs?.id as string | undefined) ?? ""; - } - }); - }); - - return { - text, - promptId: prompt, - brainId: brain, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/removeExistingMentionFromEditor.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/removeExistingMentionFromEditor.ts deleted file mode 100644 index 607faac79..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/utils/removeExistingMentionFromEditor.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Editor, JSONContent } from "@tiptap/core"; - -export const removeExistingMentionFromEditor = ( - editor: Editor, - mentionName: string -): void => { - let newContent = editor.state.doc.toJSON() as JSONContent | undefined; - - if (newContent?.content !== undefined) { - let mentionRemoved = false; - - const filteredContent = newContent.content.map((contentItem) => { - if (contentItem.content) { - const filtered = contentItem.content.filter((node) => { - if (!mentionRemoved && node.type === mentionName) { - mentionRemoved = true; - - return false; - } - - return true; - }); - - return { ...contentItem, content: filtered }; - } - - return contentItem; - }); - - newContent = { ...newContent, content: filteredContent }; - editor.commands.setContent(newContent); - } -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss deleted file mode 100644 index 01a1b9729..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -@use "styles/IconSizes.module.scss"; -@use "styles/Spacings.module.scss"; - -.menu_icon { - width: IconSizes.$normal; - height: IconSizes.$normal; - cursor: pointer; - - &:hover { - color: var(--accent); - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.tsx deleted file mode 100644 index 588ca79fa..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { GiHamburgerMenu } from "react-icons/gi"; -import { LuArrowLeftFromLine } from "react-icons/lu"; - -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import styles from "./MenuControlButton.module.scss"; - -export const MenuControlButton = (): JSX.Element => { - const { isOpened, setIsOpened } = useMenuContext(); - const { isMobile } = useDevice(); - const Icon = isOpened ? LuArrowLeftFromLine : GiHamburgerMenu; - - if (isOpened && isMobile) { - return <>; - } - - return ( - setIsOpened(!isOpened)} /> - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/hooks/useChatInput.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/hooks/useChatInput.ts deleted file mode 100644 index 82bb17d18..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/hooks/useChatInput.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback, useState } from "react"; - -import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatInput = () => { - const [message, setMessage] = useState(""); - const { addQuestion, generatingAnswer, chatId } = useChat(); - - const submitQuestion = useCallback( - (question?: string) => { - const finalMessage = question ?? message; - if (!generatingAnswer) { - void addQuestion(finalMessage, () => setMessage("")); - } - }, - [addQuestion, generatingAnswer, message] - ); - - return { - message, - setMessage, - submitQuestion, - generatingAnswer, - chatId, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss deleted file mode 100644 index 4187174b7..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; - -.chat_container { - display: flex; - flex-direction: column; - background-color: var(--background-0); - gap: Spacings.$spacing03; - border-radius: Radius.$big; - border: 1px solid var(--border-0); - overflow: hidden; - - .chat_wrapper { - display: flex; - padding: Spacings.$spacing05; - padding-top: 0; - gap: Spacings.$spacing05; - - &.disabled { - pointer-events: none; - opacity: 0.4; - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.tsx deleted file mode 100644 index 60a4b157b..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import { CurrentBrain } from "@/lib/components/CurrentBrain/CurrentBrain"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import { ChatEditor } from "./components/ChatEditor/ChatEditor"; -import { useChatInput } from "./hooks/useChatInput"; -import styles from "./index.module.scss"; - -export const ChatInput = (): JSX.Element => { - const { setMessage, submitQuestion, generatingAnswer, message } = - useChatInput(); - const { remainingCredits } = useUserSettingsContext(); - const { currentBrain } = useBrainContext(); - - const handleSubmitQuestion = () => { - if (message.trim() !== "" && remainingCredits && currentBrain) { - submitQuestion(); - } - }; - - return ( - <> -
{ - e.preventDefault(); - handleSubmitQuestion(); - }} - > -
- -
- - {generatingAnswer ? ( - - ) : ( - - )} -
-
-
- - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss deleted file mode 100644 index d30ea4ed6..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.knowledge_to_feed_wrapper { - display: flex; - flex-direction: column; - padding-block: Spacings.$spacing05; - width: 100%; - gap: Spacings.$spacing05; - overflow: hidden; - height: 100%; - - .single_selector_wrapper { - width: 30%; - min-width: 250px; - - @media (max-width: ScreenSizes.$small) { - width: 100%; - } - } - - .tabs_content_wrapper { - width: 100%; - height: 80%; - overflow: auto; - padding: Spacings.$spacing01; - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx deleted file mode 100644 index d85098f29..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; - -import { useSync } from "@/lib/api/sync/useSync"; -import { SingleSelector } from "@/lib/components/ui/SingleSelector/SingleSelector"; -import { Tabs } from "@/lib/components/ui/Tabs/Tabs"; -import { requiredRolesForUpload } from "@/lib/config/upload"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { Tab } from "@/lib/types/Tab"; - -import styles from "./KnowledgeToFeed.module.scss"; -import { FromConnections } from "./components/FromConnections/FromConnections"; -import { useFromConnectionsContext } from "./components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { FromDocuments } from "./components/FromDocuments/FromDocuments"; -import { FromWebsites } from "./components/FromWebsites/FromWebsites"; -import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput"; - -export const KnowledgeToFeed = ({ - hideBrainSelector, -}: { - hideBrainSelector?: boolean; -}): JSX.Element => { - const { allBrains, setCurrentBrainId, currentBrainId, currentBrain } = - useBrainContext(); - const [selectedTab, setSelectedTab] = useState("Documents"); - const { knowledgeToFeed } = useKnowledgeToFeedContext(); - const { openedConnections, setOpenedConnections, setCurrentSyncId } = - useFromConnectionsContext(); - const { getActiveSyncsForBrain } = useSync(); - - const brainsWithUploadRights = formatMinimalBrainsToSelectComponentInput( - useMemo( - () => - allBrains.filter( - (brain) => - requiredRolesForUpload.includes(brain.role) && !!brain.max_files - ), - [allBrains] - ) - ); - - const knowledgesTabs: Tab[] = [ - { - label: "Documents", - isSelected: selectedTab === "Documents", - onClick: () => setSelectedTab("Documents"), - iconName: "file", - badge: knowledgeToFeed.filter( - (knowledge) => knowledge.source === "upload" - ).length, - }, - { - label: "Connections", - isSelected: selectedTab === "Connections", - onClick: () => setSelectedTab("Connections"), - iconName: "sync", - badge: openedConnections.filter((connection) => connection.submitted) - .length, - }, - { - label: "Websites' page", - isSelected: selectedTab === "Websites", - onClick: () => setSelectedTab("Websites"), - iconName: "website", - badge: knowledgeToFeed.filter((knowledge) => knowledge.source === "crawl") - .length, - }, - ]; - - useEffect(() => { - if (currentBrain) { - void (async () => { - try { - const res = await getActiveSyncsForBrain(currentBrain.id); - setCurrentSyncId(undefined); - setOpenedConnections( - res.map((sync) => ({ - user_sync_id: sync.syncs_user_id, - id: sync.id, - provider: sync.syncs_user.provider, - submitted: true, - selectedFiles: { - files: [ - ...(sync.settings.folders?.map((folder) => ({ - id: folder, - name: undefined, - is_folder: true, - })) ?? []), - ...(sync.settings.files?.map((file) => ({ - id: file, - name: undefined, - is_folder: false, - })) ?? []), - ], - }, - name: sync.name, - last_synced: sync.last_synced, - })) - ); - } catch (error) { - console.error(error); - } - })(); - } - }, [currentBrainId]); - - return ( -
- {!hideBrainSelector && ( -
- -
- )} - -
- {selectedTab === "Connections" && } - {selectedTab === "Documents" && } - {selectedTab === "Websites" && } -
-
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FileLine/FileLine.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FileLine/FileLine.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FileLine/FileLine.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FileLine/FileLine.tsx deleted file mode 100644 index e3386d187..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FileLine/FileLine.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { SyncElementLine } from "../SyncElementLine/SyncElementLine"; - -interface FileLineProps { - name: string; - selectable: boolean; - id: string; - icon?: string; -} - -export const FileLine = ({ - name, - selectable, - id, - icon, -}: FileLineProps): JSX.Element => { - return ( - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FolderLine/FolderLine.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FolderLine/FolderLine.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FolderLine/FolderLine.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FolderLine/FolderLine.tsx deleted file mode 100644 index f0f0c877b..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FolderLine/FolderLine.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { SyncElementLine } from "../SyncElementLine/SyncElementLine"; - -interface FolderLineProps { - name: string; - selectable: boolean; - id: string; - icon?: string; - isAlsoFile?: boolean; -} - -export const FolderLine = ({ - name, - selectable, - id, - icon, - isAlsoFile, -}: FolderLineProps): JSX.Element => { - return ( - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.module.scss deleted file mode 100644 index fa882ff49..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.module.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.from_connection_container { - overflow: auto; - height: 100%; - padding: Spacings.$spacing01; - - .from_connection_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing06; - overflow: hidden; - max-height: 100%; - - .header_buttons { - display: flex; - justify-content: space-between; - } - - .connection_content { - overflow: auto; - flex-grow: 1; - - &.disable { - opacity: 0.5; - pointer-events: none; - } - - .loader_icon { - display: flex; - align-items: center; - justify-content: center; - } - - .empty_folder { - font-style: italic; - font-size: Typography.$small; - } - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.tsx deleted file mode 100644 index cdbf8ffc5..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnections.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useEffect, useState } from "react"; - -import { SyncElement } from "@/lib/api/sync/types"; -import { useSync } from "@/lib/api/sync/useSync"; -import { ConnectionCards } from "@/lib/components/ConnectionCards/ConnectionCards"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { TextButton } from "@/lib/components/ui/TextButton/TextButton"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import { FileLine } from "./FileLine/FileLine"; -import { FolderLine } from "./FolderLine/FolderLine"; -import styles from "./FromConnections.module.scss"; -import { useFromConnectionsContext } from "./FromConnectionsProvider/hooks/useFromConnectionContext"; - -export const FromConnections = (): JSX.Element => { - const [folderStack, setFolderStack] = useState<(string | null)[]>([]); - const { - currentSyncElements, - setCurrentSyncElements, - currentSyncId, - loadingFirstList, - setCurrentSyncId, - currentProvider, - } = useFromConnectionsContext(); - const [currentFiles, setCurrentFiles] = useState([]); - const [currentFolders, setCurrentFolders] = useState([]); - const { getSyncFiles } = useSync(); - const { userData } = useUserData(); - const [loading, setLoading] = useState(false); - - const isPremium = userData?.is_premium; - - useEffect(() => { - setCurrentFiles( - currentSyncElements?.files.filter((file) => !file.is_folder) ?? [] - ); - setCurrentFolders( - currentSyncElements?.files.filter((file) => file.is_folder) ?? [] - ); - setLoading(false); - }, [currentSyncElements]); - - const handleGetSyncFiles = async ( - userSyncId: number, - folderId: string | null - ) => { - try { - setLoading(true); - let res; - if (folderId !== null) { - res = await getSyncFiles(userSyncId, folderId); - } else { - res = await getSyncFiles(userSyncId); - } - setCurrentSyncElements(res); - } catch (error) { - console.error("Failed to get sync files:", error); - } - }; - - const handleBackClick = async () => { - if (folderStack.length > 0 && currentSyncId) { - const newFolderStack = [...folderStack]; - newFolderStack.pop(); - setFolderStack(newFolderStack); - const parentFolderId = newFolderStack[newFolderStack.length - 1]; - await handleGetSyncFiles(currentSyncId, parentFolderId); - } else { - setCurrentSyncElements({ files: [] }); - } - }; - - const handleFolderClick = async (userSyncId: number, folderId: string) => { - setFolderStack([...folderStack, folderId]); - await handleGetSyncFiles(userSyncId, folderId); - }; - - return ( -
- {!currentSyncId && !loadingFirstList ? ( - - ) : ( -
-
- { - if (folderStack.length) { - void handleBackClick(); - } else { - setCurrentSyncId(undefined); - } - }} - small={true} - disabled={loading || loadingFirstList} - /> -
-
- {loading || loadingFirstList ? ( -
- -
- ) : ( - <> - {currentFolders.map((folder) => ( -
{ - if (currentSyncId) { - void handleFolderClick(currentSyncId, folder.id); - } - }} - > - -
- ))} - {currentFiles.map((file) => ( -
- -
- ))} - - )} - {!currentFiles.length && - !currentFolders.length && - !loading && - !loadingFirstList && ( - Empty folder - )} -
-
- )} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/FromConnection-provider.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/FromConnection-provider.tsx deleted file mode 100644 index ac83aa192..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/FromConnection-provider.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { createContext, useState } from "react"; - -import { OpenedConnection, Provider, SyncElements } from "@/lib/api/sync/types"; - -export type FromConnectionsContextType = { - currentSyncElements: SyncElements | undefined; - setCurrentSyncElements: React.Dispatch< - React.SetStateAction - >; - currentSyncId: number | undefined; - setCurrentSyncId: React.Dispatch>; - openedConnections: OpenedConnection[]; - setOpenedConnections: React.Dispatch< - React.SetStateAction - >; - hasToReload: boolean; - setHasToReload: React.Dispatch>; - loadingFirstList: boolean; - setLoadingFirstList: React.Dispatch>; - currentProvider: Provider | null; - setCurrentProvider: React.Dispatch>; -}; - -export const FromConnectionsContext = createContext< - FromConnectionsContextType | undefined ->(undefined); - -export const FromConnectionsProvider = ({ - children, -}: { - children: React.ReactNode; -}): JSX.Element => { - const [currentSyncElements, setCurrentSyncElements] = useState< - SyncElements | undefined - >(undefined); - const [currentSyncId, setCurrentSyncId] = useState( - undefined - ); - const [openedConnections, setOpenedConnections] = useState< - OpenedConnection[] - >([]); - const [currentProvider, setCurrentProvider] = useState(null); - const [hasToReload, setHasToReload] = useState(false); - const [loadingFirstList, setLoadingFirstList] = useState(false); - - return ( - - {children} - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext.tsx deleted file mode 100644 index 034d2b01c..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useContext } from "react"; - -import { - FromConnectionsContext, - FromConnectionsContextType, -} from "../FromConnection-provider"; - -export const useFromConnectionsContext = (): FromConnectionsContextType => { - const context = useContext(FromConnectionsContext); - if (context === undefined) { - throw new Error( - "useFromConnectionsContext must be used within a FromConnectionsProvider" - ); - } - - return context; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.module.scss deleted file mode 100644 index ae3d66360..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.sync_element_line_wrapper { - display: flex; - justify-content: space-between; - padding: Spacings.$spacing03; - border-top: 1px solid var(--border-1); - align-items: center; - cursor: pointer; - font-weight: 500; - - .left { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - overflow: hidden; - - .element_name { - font-size: Typography.$small; - @include Typography.EllipsisOverflow; - } - - &.folder { - margin-left: Spacings.$spacing06; - } - } - - &:hover { - background-color: var(--background-3); - } - - &.no_hover { - cursor: default; - - &:hover { - background-color: var(--background-0); - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.tsx deleted file mode 100644 index 40b698bc8..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/SyncElementLine/SyncElementLine.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useState } from "react"; - -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; - -import styles from "./SyncElementLine.module.scss"; - -import { useFromConnectionsContext } from "../FromConnectionsProvider/hooks/useFromConnectionContext"; - -interface SyncElementLineProps { - name: string; - selectable: boolean; - id: string; - isFolder: boolean; - icon?: string; - isAlsoFile?: boolean; -} - -export const SyncElementLine = ({ - name, - selectable, - id, - isFolder, - icon, - isAlsoFile, -}: SyncElementLineProps): JSX.Element => { - const [isCheckboxHovered, setIsCheckboxHovered] = useState(false); - const { currentSyncId, openedConnections, setOpenedConnections } = - useFromConnectionsContext(); - - const initialChecked = (): boolean => { - const currentConnection = openedConnections.find( - (connection) => connection.user_sync_id === currentSyncId - ); - - return currentConnection - ? currentConnection.selectedFiles.files.some((file) => file.id === id) - : false; - }; - - const [checked, setChecked] = useState(initialChecked); - - const showCheckbox: boolean = isAlsoFile ?? selectable; - - const handleSetChecked = () => { - setOpenedConnections((prevState) => { - return prevState.map((connection) => { - if (connection.user_sync_id === currentSyncId) { - const isFileSelected = connection.selectedFiles.files.some( - (file) => file.id === id - ); - const updatedFiles = isFileSelected - ? connection.selectedFiles.files.filter((file) => file.id !== id) - : [ - ...connection.selectedFiles.files, - { id, name, is_folder: isFolder }, - ]; - - return { - ...connection, - selectedFiles: { - files: updatedFiles, - }, - }; - } - - return connection; - }); - }); - setChecked((prevChecked) => !prevChecked); - }; - - const content = ( -
{ - if (isFolder && checked) { - event.stopPropagation(); - } - }} - > -
- {showCheckbox && ( -
setIsCheckboxHovered(true)} - onMouseLeave={() => setIsCheckboxHovered(false)} - style={{ pointerEvents: "auto" }} - > - -
- )} - {icon ? ( -
{icon}
- ) : ( - - )} - {name} -
- {isFolder && ( - - )} -
- ); - - return selectable ? ( - content - ) : ( - - {content} - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss deleted file mode 100644 index 099fd78d6..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss +++ /dev/null @@ -1,47 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; - -.from_document_wrapper { - width: 100%; - border: 1px dashed var(--border-0); - border-radius: Radius.$big; - box-sizing: border-box; - cursor: pointer; - height: 100%; - overflow: auto; - transition: border-color 0.3s ease 0.2s, border-width 0.1s ease 0.1s; - - &.dragging { - border: 3px dashed var(--accent); - background-color: var(--background-3); - } - - &:hover { - border: 3px dashed var(--accent); - } - - .box_content { - padding: Spacings.$spacing05; - display: flex; - flex-direction: column; - column-gap: Spacings.$spacing05; - justify-content: center; - align-items: center; - height: 100%; - - .input { - display: flex; - gap: Spacings.$spacing02; - padding: Spacings.$spacing05; - - @media (max-width: ScreenSizes.$small) { - flex-direction: column; - } - - .clickable { - font-weight: bold; - } - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.tsx deleted file mode 100644 index 4bbe25061..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useCustomDropzone } from "@/lib/hooks/useDropzone"; - -import styles from "./FromDocuments.module.scss"; - -export const FromDocuments = (): JSX.Element => { - const [dragging, setDragging] = useState(false); - const { getRootProps, getInputProps, open } = useCustomDropzone(); - const { knowledgeToFeed } = useKnowledgeToFeedContext(); - - useEffect(() => { - setDragging(false); - }, [knowledgeToFeed]); - - return ( -
setDragging(true)} - onDragLeave={() => setDragging(false)} - onMouseLeave={() => setDragging(false)} - onClick={open} - > -
- -
-
- Choose files - -
- or drag it here -
-
-
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromWebsites/FromWebsites.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromWebsites/FromWebsites.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromWebsites/FromWebsites.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromWebsites/FromWebsites.tsx deleted file mode 100644 index d13b6ff69..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromWebsites/FromWebsites.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useCrawler } from "@/lib/components/KnowledgeToFeedInput/components/Crawler/hooks/useCrawler"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; - -import styles from "./FromWebsites.module.scss"; - -export const FromWebsites = (): JSX.Element => { - const { handleSubmit, urlToCrawl, setUrlToCrawl } = useCrawler(); - - return ( -
- handleSubmit()} - /> -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/hooks/useFeedBrainInChat.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/hooks/useFeedBrainInChat.ts deleted file mode 100644 index 9c2359ebb..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/hooks/useFeedBrainInChat.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*eslint max-lines: ["error", 200 ]*/ - -import { useQueryClient } from "@tanstack/react-query"; -import { UUID } from "crypto"; -import { useParams, useRouter } from "next/navigation"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { CHATS_DATA_KEY } from "@/lib/api/chat/config"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useNotificationApi } from "@/lib/api/notification/useNotificationApi"; -import { useKnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts"; -import { useChatContext } from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useToast } from "@/lib/hooks"; -import { useOnboarding } from "@/lib/hooks/useOnboarding"; - -import { FeedItemCrawlType, FeedItemUploadType } from "../../../types"; -import { useFromConnectionsContext } from "../components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useFeedBrainInChat = ({ - dispatchHasPendingRequests, -}: { - dispatchHasPendingRequests: () => void; -}) => { - const { publish } = useToast(); - const queryClient = useQueryClient(); - const { t } = useTranslation(["upload"]); - const router = useRouter(); - const { updateOnboarding, onboarding } = useOnboarding(); - const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); - const { currentBrainId } = useBrainContext(); - const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext(); - const [hasPendingRequests, setHasPendingRequests] = useState(false); - const { createChat } = useChatApi(); - const params = useParams(); - const chatId = params?.chatId as UUID | undefined; - const { setNotifications } = useChatContext(); - const { getChatNotifications } = useNotificationApi(); - const fetchNotifications = async (currentChatId: UUID): Promise => { - const fetchedNotifications = await getChatNotifications(currentChatId); - setNotifications(fetchedNotifications); - }; - const { openedConnections } = useFromConnectionsContext(); - const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput(); - const files: File[] = ( - knowledgeToFeed.filter((c) => c.source === "upload") as FeedItemUploadType[] - ).map((c) => c.file); - const urls: string[] = ( - knowledgeToFeed.filter((c) => c.source === "crawl") as FeedItemCrawlType[] - ).map((c) => c.url); - const feedBrain = async (): Promise => { - if (currentBrainId === null) { - publish({ - variant: "danger", - text: t("selectBrainFirst"), - }); - - return; - } - if (knowledgeToFeed.length === 0 && !openedConnections.length) { - publish({ - variant: "danger", - text: t("addFiles"), - }); - - return; - } - try { - dispatchHasPendingRequests(); - setShouldDisplayFeedCard(false); - setHasPendingRequests(true); - const currentChatId = chatId ?? (await createChat("New Chat")).chat_id; - const uploadPromises = files.map((file) => - uploadFileHandler(file, currentBrainId, currentChatId) - ); - const crawlPromises = urls.map((url) => - crawlWebsiteHandler(url, currentBrainId, currentChatId) - ); - - const updateOnboardingPromise = async () => { - if (onboarding.onboarding_a) { - await updateOnboarding({ - onboarding_a: false, - }); - } - }; - - await Promise.all([ - ...uploadPromises, - ...crawlPromises, - updateOnboardingPromise(), - ]); - - setKnowledgeToFeed([]); - - if (chatId === undefined) { - void queryClient.invalidateQueries({ - queryKey: [CHATS_DATA_KEY], - }); - void router.push(`/chat/${currentChatId}`); - } else { - await fetchNotifications(currentChatId); - } - } catch (e) { - publish({ - variant: "danger", - text: JSON.stringify(e), - }); - } finally { - setHasPendingRequests(false); - } - }; - - return { - feedBrain, - hasPendingRequests, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/index.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/index.ts deleted file mode 100644 index 87c3aadd3..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./KnowledgeToFeed"; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/utils/formatMinimalBrainsToSelectComponentInput.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/utils/formatMinimalBrainsToSelectComponentInput.ts deleted file mode 100644 index 306f95deb..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/utils/formatMinimalBrainsToSelectComponentInput.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { UUID } from "crypto"; - -import { SelectOptionProps } from "@/lib/components/ui/Select"; -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -export const formatMinimalBrainsToSelectComponentInput = ( - brains: MinimalBrainForUser[] -): SelectOptionProps[] => - brains.map((brain) => ({ - label: brain.name, - value: brain.id, - })); diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/index.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/index.ts deleted file mode 100644 index 77e5a4298..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ChatInput"; -export * from "./KnowledgeToFeed"; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/hooks/useActionBar.ts b/frontend/app/chat/[chatId]/components/ActionsBar/hooks/useActionBar.ts deleted file mode 100644 index f9d91a64a..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/hooks/useActionBar.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect, useState } from "react"; - -import { useChatContext } from "@/lib/context"; - -import { checkIfHasPendingRequest } from "../utils/checkIfHasPendingRequest"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useActionBar = () => { - const [hasPendingRequests, setHasPendingRequests] = useState(false); - const { notifications } = useChatContext(); - - useEffect(() => { - setHasPendingRequests(checkIfHasPendingRequest(notifications)); - }, [notifications]); - - return { - hasPendingRequests, - setHasPendingRequests, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/index.ts b/frontend/app/chat/[chatId]/components/ActionsBar/index.ts deleted file mode 100644 index 4d51b4d8f..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ActionsBar"; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/types.ts b/frontend/app/chat/[chatId]/components/ActionsBar/types.ts deleted file mode 100644 index f377129c0..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const mentionTriggers = ["@"] as const; - -export type MentionTriggerType = (typeof mentionTriggers)[number]; -export type FeedItemSource = "crawl" | "upload"; - -export type FeedItemCrawlType = { - source: "crawl"; - url: string; -}; - -export type FeedItemUploadType = { - source: "upload"; - file: File; -}; - -export type FeedItemType = FeedItemCrawlType | FeedItemUploadType; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/utils/checkIfHasPendingRequest.ts b/frontend/app/chat/[chatId]/components/ActionsBar/utils/checkIfHasPendingRequest.ts deleted file mode 100644 index 30f6e7c2d..000000000 --- a/frontend/app/chat/[chatId]/components/ActionsBar/utils/checkIfHasPendingRequest.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Notification } from "../../../types"; - -export const checkIfHasPendingRequest = ( - notifications: Notification[] -): boolean => { - return notifications.some((item) => item.status === "Pending"); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/ChatDialogue.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/ChatDialogue.tsx deleted file mode 100644 index 226699b8a..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/ChatDialogue.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useChatContext } from "@/lib/context"; -import { useOnboarding } from "@/lib/hooks/useOnboarding"; - -import { ChatDialogue } from "./components/ChatDialogue"; -import { getMergedChatMessagesWithDoneStatusNotificationsReduced } from "./utils/getMergedChatMessagesWithDoneStatusNotificationsReduced"; - -export const ChatDialogueArea = (): JSX.Element => { - const { messages, notifications } = useChatContext(); - - const chatItems = getMergedChatMessagesWithDoneStatusNotificationsReduced( - messages, - notifications - ); - const { isOnboarding } = useOnboarding(); - - const shouldDisplayShortcuts = chatItems.length === 0 && !isOnboarding; - - if (!shouldDisplayShortcuts) { - return ; - } - - return <>; -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatItem.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatItem.tsx deleted file mode 100644 index 31ebf20f1..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatItem.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ChatNotification } from "./ChatNotification/ChatNotification"; -import { QADisplay } from "./QADisplay"; - -import { ChatItemWithGroupedNotifications } from "../../../../types"; - -type ChatItemProps = { - content: ChatItemWithGroupedNotifications; - index: number; - lastMessage?: boolean; -}; -export const ChatItem = ({ - content, - index, - lastMessage, -}: ChatItemProps): JSX.Element => { - if (content.item_type === "MESSAGE") { - return ( - - ); - } - - return ; -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/ChatNotification.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/ChatNotification.tsx deleted file mode 100644 index 688dd0eba..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/ChatNotification.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Notification } from "@/app/chat/[chatId]/types"; - -import { NotificationDisplayer } from "./components"; - -type NotificationProps = { - content: Notification[]; -}; - -export const ChatNotification = ({ - content, -}: NotificationProps): JSX.Element => { - return ( -
- {content.map((notification) => ( - - ))} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/NotificationDisplayer.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/NotificationDisplayer.tsx deleted file mode 100644 index 3d901e2b4..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/NotificationDisplayer.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Fragment, useState } from "react"; -import { MdLink } from "react-icons/md"; - -import { Notification } from "@/app/chat/[chatId]/types"; -import { getFileIcon } from "@/lib/helpers/getFileIcon"; - -import { NotificationMessage, notificationStatusToIcon } from "./types"; - -type NotificationDisplayerProps = { - content: Notification; -}; - -export const NotificationDisplayer = ({ - content, -}: NotificationDisplayerProps): JSX.Element => { - const { message: nonParsedMessage, action } = content; - const [isHovered, setIsHovered] = useState(false); - - if (nonParsedMessage === null || nonParsedMessage === undefined) { - return ; - } - - let message, status, name; - - try { - const parsedMessage = JSON.parse( - nonParsedMessage.replace(/'/g, '"') - ) as NotificationMessage; - - message = parsedMessage.message; - status = parsedMessage.status; - name = parsedMessage.name; - } catch (error) { - return ; - } - - return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - className="relative flex flex-row p-2 gap-1 rounded-sm items-center hover:bg-gray-100 transition duration-300 cursor-pointer" - > - - {notificationStatusToIcon[status]} - -
-
-
- {action === "CRAWL" ? : getFileIcon(name)} -
- {name} -
-
- {isHovered && ( -
- {message} -
- )} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/types.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/types.ts deleted file mode 100644 index a4ebcc698..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/NotificationDisplayer/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type NotificationMessage = { - message: string; - status: "warning" | "error" | "success"; - name: string; -}; -export const notificationStatusToIcon = { - warning: "âš ï¸", - error: "âŒ", - success: "✅", -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/index.ts deleted file mode 100644 index 45d6270ea..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/ChatNotification/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./NotificationDisplayer/NotificationDisplayer"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/QADisplay.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/QADisplay.tsx deleted file mode 100644 index 4e1df7f38..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/QADisplay.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ChatMessage } from "@/app/chat/[chatId]/types"; - -import { MessageRow } from "./components/MessageRow/MessageRow"; -import "./styles.css"; - -type QADisplayProps = { - content: ChatMessage; - index: number; - lastMessage?: boolean; -}; -export const QADisplay = ({ - content, - index, - lastMessage, -}: QADisplayProps): JSX.Element => { - const { assistant, message_id, user_message, brain_name, metadata, thumbs } = - content; - - return ( - <> - - - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.module.scss deleted file mode 100644 index f33b6dd8c..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.module.scss +++ /dev/null @@ -1,182 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Transitions.module.scss"; -@use "styles/Typography.module.scss"; - -.message_row_container { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - border-bottom: 1px solid var(--border-0); - padding-bottom: Spacings.$spacing01; - position: relative; - - &.user { - font-size: Typography.$very_large; - font-weight: 500; - border-bottom: none; - border-radius: Radius.$normal; - - p { - display: -webkit-box; - -webkit-line-clamp: 1000; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - - &:hover { - cursor: pointer; - } - } - } - - &.last { - border-bottom: none; - } - - &.smaller { - font-size: Typography.$large; - } - - &.folded { - &.user { - p { - -webkit-line-clamp: 6; - } - } - } - - @media screen and (max-width: ScreenSizes.$small) { - &.user { - font-size: Typography.$large; - } - } - - .icon_rotate { - position: absolute; - right: Spacings.$spacing04; - top: -(Spacings.$spacing05); - transition: transform 0.3s Transitions.$easeOutBack; - } - - .icon_rotate_down { - transform: rotate(0deg); - } - - .icon_rotate_up { - transform: rotate(-180deg); - } - - .message_row_content { - border-radius: Radius.$big; - width: fit-content; - padding-block: Spacings.$spacing03; - } - - .message_header_wrapper { - overflow: hidden; - } - - &.brain { - .message_header { - display: flex; - gap: Spacings.$spacing04; - align-items: baseline; - @include Typography.H2; - } - - .message_row_content { - align-self: flex-start; - position: relative; - padding: 0; - } - - .metadata_wrapper { - display: flex; - flex-direction: column; - left: Spacings.$spacing02; - top: calc(100% + #{Spacings.$spacing03}); - gap: Spacings.$spacing03; - - .sources_and_citations_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - max-width: 100%; - padding-bottom: Spacings.$spacing05; - - .title_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .title { - @include Typography.H2; - } - } - - .sources { - display: flex; - column-gap: Spacings.$spacing06; - row-gap: Spacings.$spacing03; - flex-wrap: wrap; - max-width: 100%; - } - } - - .icons_wrapper { - display: flex; - gap: Spacings.$spacing04; - padding-top: Spacings.$spacing03; - padding-bottom: Spacings.$spacing05; - width: 100%; - justify-content: flex-end; - - .with_border { - border-bottom: 1px solid var(--border-0); - } - } - } - - .related_questions_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - padding-block: Spacings.$spacing03; - - .title_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .title { - @include Typography.H2; - } - } - - .questions_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing02; - font-size: Typography.$small; - - .question { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .text { - color: var(--text-4); - transition: color 0.5s ease; - - &:hover { - color: var(--text-3); - cursor: pointer; - } - } - } - } - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.tsx deleted file mode 100644 index a54b78627..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/MessageRow.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { UUID } from "crypto"; -import React, { useEffect, useState } from "react"; - -import { useChatInput } from "@/app/chat/[chatId]/components/ActionsBar/components/ChatInput/hooks/useChatInput"; -import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { Integration } from "@/lib/api/sync/types"; -import { CopyButton } from "@/lib/components/ui/CopyButton"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { Source } from "@/lib/types/MessageMetadata"; - -import styles from "./MessageRow.module.scss"; -import { MessageContent } from "./components/MessageContent/MessageContent"; -import { QuestionBrain } from "./components/QuestionBrain/QuestionBrain"; -import { SourceCitations } from "./components/Source/Source"; -import { useMessageRow } from "./hooks/useMessageRow"; -import { SourceFile } from "./types/types"; - -type MessageRowProps = { - speaker: "user" | "assistant"; - text?: string; - brainName?: string | null; - children?: React.ReactNode; - metadata?: { - sources?: Source[]; - thoughts?: string; - followup_questions?: string[]; - snippet_color?: string; - snippet_emoji?: string; - metadata_model?: { - display_name: string; - image_url: string; - brain_id: UUID; - }; - }; - index?: number; - messageId?: string; - thumbs?: boolean; - lastMessage?: boolean; -}; - -export const MessageRow = ({ - speaker, - text, - brainName, - children, - messageId, - thumbs: initialThumbs, - metadata, - lastMessage, -}: MessageRowProps): JSX.Element => { - const { handleCopy, isUserSpeaker } = useMessageRow({ - speaker, - text, - }); - const { updateChatMessage } = useChatApi(); - const { chatId } = useChat(); - const [thumbs, setThumbs] = useState( - initialThumbs - ); - const [folded, setFolded] = useState(false); - const [userMessageFolded, setUserMessageFolded] = useState(true); - const [sourceFiles, setSourceFiles] = useState([]); - const { submitQuestion } = useChatInput(); - - useEffect(() => { - setThumbs(initialThumbs); - setSourceFiles( - metadata?.sources?.reduce((acc, source) => { - const existingSource = acc.find((s) => s.filename === source.name); - if (existingSource) { - existingSource.citations.push(source.citation); - } else { - acc.push({ - filename: source.name, - file_url: source.source_url, - citations: [source.citation], - selected: false, - integration: source.integration as Integration, - integration_link: source.integration_link, - }); - } - - return acc; - }, [] as SourceFile[]) ?? [] - ); - }, [initialThumbs, metadata]); - - const messageContent = text ?? ""; - - const userMessageTooLong = (): boolean => { - return !!isUserSpeaker && !!messageContent && messageContent.length > 100; - }; - - const thumbsUp = async () => { - if (chatId && messageId) { - await updateChatMessage(chatId, messageId, { - thumbs: thumbs ? null : true, - }); - setThumbs(thumbs ? null : true); - } - }; - - const thumbsDown = async () => { - if (chatId && messageId) { - await updateChatMessage(chatId, messageId, { - thumbs: thumbs === false ? null : false, - }); - setThumbs(thumbs === false ? null : false); - } - }; - - const renderMessageHeader = () => { - if (!isUserSpeaker && !folded) { - return ( -
-
- -
-
- ); - } - }; - - const renderMetadata = () => { - if (!isUserSpeaker && messageContent !== "🧠") { - return ( -
-
- - { - await thumbsUp(); - }} - /> - { - await thumbsDown(); - }} - /> -
- - {sourceFiles.length > 0 && ( -
-
- - Sources -
-
- {sourceFiles.map((sourceFile, i) => ( -
- -
- ))} -
-
- )} -
- ); - } - }; - - const renderRelatedQuestions = () => { - if ( - !isUserSpeaker && - !folded && - (metadata?.followup_questions?.length ?? 0) > 0 - ) { - return ( -
-
- - Follow up questions -
-
- {metadata?.followup_questions?.map((question, index) => ( -
submitQuestion(question)} - > - - {question} -
- ))} -
-
- ); - } - }; - - const renderOtherSections = () => { - return ( - <> - {!folded && renderMetadata()} - {!folded && renderRelatedQuestions()} - - ); - }; - - return ( -
- {!isUserSpeaker && messageContent !== "🧠" && ( -
setFolded(!folded)}> - -
- )} - {renderMessageHeader()} -
- {children ?? ( -
{ - if (isUserSpeaker) { - setUserMessageFolded(!userMessageFolded); - } - }} - > - -
- )} -
- {renderOtherSections()} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.module.scss deleted file mode 100644 index df50b3225..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.modal_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - - .title_wrapper { - display: flex; - gap: Spacings.$spacing03; - align-items: baseline; - overflow: hidden; - padding-right: Spacings.$spacing05; - - .title { - white-space: nowrap; - } - - .file_link { - font-weight: 600; - overflow: hidden; - @include Typography.EllipsisOverflow; - - .filename { - @include Typography.EllipsisOverflow; - } - } - } - - .citation { - font-size: Typography.$small; - font-style: italic; - } -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.tsx deleted file mode 100644 index 092de72f6..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/CitationModal/CitationModal.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Modal } from "@/lib/components/ui/Modal/Modal"; - -import styles from "./CitationModal.module.scss"; - -import { SourceFile } from "../../types/types"; - -type CitationModalProps = { - citation: string; - sourceFile: SourceFile; - isModalOpened: boolean; - setIsModalOpened: (isModalOpened: boolean) => void; -}; -export const CitationModal = ({ - citation, - sourceFile, - isModalOpened, - setIsModalOpened, -}: CitationModalProps): JSX.Element => { - return ( - } - > -
-
- Text extract from: - - {sourceFile.filename} - -
- {citation} -
-
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss deleted file mode 100644 index 22f7bd5d7..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss +++ /dev/null @@ -1,140 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.hiden { - display: none; -} - -.markdown { - font-size: Typography.$small; - p { - margin: 0; - padding: 0; - align-items: center; - } - - ul { - list-style-type: disc; - margin-top: 0; - padding: 0; - margin-left: Spacings.$spacing05; - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - - li { - white-space-collapse: collapse; - } - } - - ol { - list-style-type: decimal; - padding-left: Spacings.$spacing05; - list-style-position: outside; - - li { - white-space-collapse: collapse; - } - } - - h1 { - @include Typography.H1; - } - - h2 { - @include Typography.H2; - } - - h3 { - @include Typography.H3; - } - - table { - width: 100%; - border-collapse: collapse; - margin: Spacings.$spacing05 0; - } - - thead { - background-color: var(--background-1); - } - - tr { - border-bottom: 1px solid var(--border-0); - } - - th, - td { - padding: Spacings.$spacing03; - text-align: left; - } - - th { - font-weight: bold; - } - - pre[class*="language-"] { - background: var(--background-5); - color: var(--white-0); - padding: Spacings.$spacing05; - border-radius: Radius.$normal; - overflow: auto; - margin: 0 0 Spacings.$spacing05 0; - white-space: pre-wrap; - font-size: Typography.$small; - font-family: "Courier New", Courier, monospace; - } - - code[class*="language-"] { - background: none; - color: inherit; - border-radius: Radius.$normal; - font-family: "Courier New", Courier, monospace; - font-size: Typography.$small; - white-space: pre-wrap; - } - - code { - background: var(--background-5); - color: var(--white-0); - padding: Spacings.$spacing01; - border-radius: Radius.$normal; - font-family: "Courier New", Courier, monospace; - font-size: Typography.$medium; - } - - .code_block { - .icon { - position: absolute; - right: 0; - padding: Spacings.$spacing05; - } - code { - white-space: pre-wrap; - } - } -} - -.thinking { - animation: pulse 1s infinite; - font-size: 2px; - line-height: 16px; - width: 16px; - display: flex; - justify-content: center; -} - -@keyframes pulse { - 0% { - font-size: 6px; - } - - 50% { - font-size: 16px; - } - - 100% { - font-size: 6px; - } -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.tsx deleted file mode 100644 index 9aa22581f..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/MessageContent/MessageContent.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import "katex/dist/katex.min.css"; -import Prism from "prismjs"; -import "prismjs/components/prism-bash"; -import "prismjs/components/prism-basic"; -import "prismjs/components/prism-c"; -import "prismjs/components/prism-cpp"; -import "prismjs/components/prism-csharp"; -import "prismjs/components/prism-css"; -import "prismjs/components/prism-dart"; -import "prismjs/components/prism-go"; -import "prismjs/components/prism-java"; -import "prismjs/components/prism-jsx"; -import "prismjs/components/prism-kotlin"; -import "prismjs/components/prism-markup"; -import "prismjs/components/prism-python"; -import "prismjs/components/prism-ruby"; -import "prismjs/components/prism-rust"; -import "prismjs/components/prism-scss"; -import "prismjs/components/prism-sql"; -import "prismjs/components/prism-swift"; -import "prismjs/components/prism-tsx"; -import "prismjs/components/prism-typescript"; -import { useEffect, useState } from "react"; -import { BlockMath, InlineMath } from "react-katex"; -import ReactMarkdown from "react-markdown"; -import gfm from "remark-gfm"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import styles from "./MessageContent.module.scss"; - -export const MessageContent = ({ - text, - isUser, - hide, -}: { - text: string; - isUser: boolean; - hide: boolean; -}): JSX.Element => { - const [showLog] = useState(true); - const [isLog, setIsLog] = useState(true); - - const extractLog = (log: string) => { - const logRegex = /🧠<([^>]+)>🧠/g; - const logs = []; - let match; - - while ((match = logRegex.exec(log))) { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - logs.push("- " + match[1] + " \n"); - } - - return { - logs: logs.join(""), - cleanedText: log.replace(logRegex, ""), - }; - }; - - useEffect(() => { - setIsLog(text.includes("🧠<")); - }, [text]); - - const { logs, cleanedText } = extractLog(text); - - return ( -
- {isLog && showLog && logs.length > 0 && ( -
- {logs} -
- )} - { - const match = /language-(\w+)/.exec(className ?? ""); - if (match) { - const language = match[1]; - const code = String(children).trim(); - - if ( - language === "math" || - language === "latex" || - language === "katex" || - language === "katex-error" - ) { - return ; - } else if (language === "inline-math") { - return ; - } - - if (Prism.languages[language]) { - const html = Prism.highlight( - code, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - Prism.languages[language], - language - ); - - return ( -
-
{ - void navigator.clipboard.writeText(code); - }} - > - -
-
-                      
-                    
-
- ); - } else { - console.error( - `The language '${language}' has no grammar loaded.` - ); - - return ( -
-
-                      {children}
-                    
-
- ); - } - } - - return ( - - {children} - - ); - }, - }} - > - {cleanedText} -
-
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss deleted file mode 100644 index ce23933de..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_name_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - color: var(--text-3); - overflow: hidden; - - .brain_image { - border-radius: Radius.$normal; - } - - .brain_name { - @include Typography.EllipsisOverflow; - } - - .brain_snippet { - width: 24px; - height: 24px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$tiny; - } -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx deleted file mode 100644 index 5d889ccc7..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import Image from "next/image"; -import { Fragment } from "react"; - -import styles from "./QuestionBrain.module.scss"; - -type QuestionBrainProps = { - brainName?: string | null; - imageUrl?: string; - snippetColor?: string; - snippetEmoji?: string; -}; -export const QuestionBrain = ({ - brainName, - imageUrl, - snippetColor, - snippetEmoji, -}: QuestionBrainProps): JSX.Element => { - if (brainName === undefined || brainName === null) { - return ; - } - - return ( -
- {imageUrl ? ( - - ) : ( -
- {snippetEmoji} -
- )} - {brainName} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.module.scss deleted file mode 100644 index d0f572309..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.module.scss +++ /dev/null @@ -1,61 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.source_and_citations_container { - display: flex; - flex-direction: column; - gap: Spacings.$spacing01; - max-width: 240px; - - .source_wrapper { - padding: Spacings.$spacing03; - border-radius: Radius.$normal; - border: 1px solid var(--border-1); - width: fit-content; - max-width: 100%; - overflow: hidden; - display: flex; - gap: Spacings.$spacing03; - cursor: pointer; - - .source_header { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - font-size: Typography.$tiny; - overflow: hidden; - width: 100%; - justify-content: space-between; - color: var(--text-2); - max-width: 200px; - - .filename { - @include Typography.EllipsisOverflow; - } - } - - &:hover { - border-color: var(--primary-0); - transition: border-color 0.3s ease; - color: var(--primary-0); - } - } -} - -.citations_container { - display: flex; - gap: Spacings.$spacing02; - flex-wrap: wrap; - - .citation_index { - cursor: pointer; - color: var(--text-4); - font-size: Typography.$tiny; - - &:hover { - color: var(--primary-0); - } - } -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.tsx deleted file mode 100644 index 658156bea..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/components/Source/Source.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import Image from "next/image"; -import { useState } from "react"; - -import { useSync } from "@/lib/api/sync/useSync"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; - -import styles from "./Source.module.scss"; - -import { SourceFile } from "../../types/types"; -import { CitationModal } from "../CitationModal/CitationModal"; - -type SourceProps = { - sourceFile: SourceFile; -}; -export const SourceCitations = ({ sourceFile }: SourceProps): JSX.Element => { - const [isExpanded, setIsExpanded] = useState(false); - const [hovered, isHovered] = useState(false); - const [isCitationModalOpened, setIsCitationModalOpened] = - useState(false); - const [citationIndex, setCitationIndex] = useState(0); - const { integrationIconUrls } = useSync(); - - return ( -
-
- -
- {sourceFile.citations.map((citation, i) => ( -
- - { - setIsCitationModalOpened(true); - setCitationIndex(i); - }} - > - [{i + 1}] - - -
- ))} -
-
- {isCitationModalOpened && ( - - )} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/hooks/useMessageRow.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/hooks/useMessageRow.ts deleted file mode 100644 index 9e3980f37..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/hooks/useMessageRow.ts +++ /dev/null @@ -1,21 +0,0 @@ -type UseMessageRowProps = { - speaker: "user" | "assistant"; - text?: string; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useMessageRow = ({ speaker, text }: UseMessageRowProps) => { - const isUserSpeaker = speaker === "user"; - - const handleCopy = () => { - if (text === undefined) { - return; - } - navigator.clipboard.writeText(text).catch((err) => console.error(err)); - }; - - return { - isUserSpeaker, - handleCopy, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/index.ts deleted file mode 100644 index 324d60a14..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./MessageRow"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/types/types.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/types/types.ts deleted file mode 100644 index 0af6ad815..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/components/MessageRow/types/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Integration } from "@/lib/api/sync/types"; - -export interface SourceFile { - filename: string; - file_url: string; - citations: string[]; - selected: boolean; - integration?: Integration; - integration_link?: string; -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/index.ts deleted file mode 100644 index 534af9e42..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./QADisplay"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/styles.css b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/styles.css deleted file mode 100644 index f2ab941f5..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/QADisplay/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -* { - white-space: pre-line; -} diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/index.ts deleted file mode 100644 index 75056356a..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/ChatItem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ChatItem"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/index.ts deleted file mode 100644 index 40b1b0ddc..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ChatItem"; -export * from "./ChatItem/QADisplay"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/hooks/useChatDialogue.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/hooks/useChatDialogue.ts deleted file mode 100644 index b4e9ac41c..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/hooks/useChatDialogue.ts +++ /dev/null @@ -1,53 +0,0 @@ -import _debounce from "lodash/debounce"; -import { useCallback, useEffect, useRef } from "react"; - -import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; - -//TODO: link this to chat input to get the right height -const chatInputHeightEstimation = 100; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatDialogue = () => { - const chatListRef = useRef(null); - const { messages } = useChat(); - const { shouldDisplayFeedCard } = useKnowledgeToFeedContext(); - - const scrollToBottom = useCallback( - _debounce(() => { - if (chatListRef.current) { - chatListRef.current.scrollTo({ - top: chatListRef.current.scrollHeight, - behavior: "auto", - }); - } - }, 100), - [] - ); - - useEffect(() => { - const computeCardHeight = () => { - if (chatListRef.current) { - const cardTop = chatListRef.current.getBoundingClientRect().top; - const windowHeight = window.innerHeight; - const cardHeight = windowHeight - cardTop - chatInputHeightEstimation; - chatListRef.current.style.height = `${cardHeight}px`; - } - }; - - computeCardHeight(); - window.addEventListener("resize", computeCardHeight); - - return () => { - window.removeEventListener("resize", computeCardHeight); - }; - }, []); - - useEffect(() => { - scrollToBottom(); - }, [messages, scrollToBottom, shouldDisplayFeedCard]); - - return { - chatListRef, - }; -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/index.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/index.tsx deleted file mode 100644 index bc67ad0ea..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { useOnboarding } from "@/lib/hooks/useOnboarding"; - -import { ChatItem } from "./components"; -import { useChatDialogue } from "./hooks/useChatDialogue"; -import { - chatDialogueContainerClassName, - chatItemContainerClassName, -} from "./styles"; -import { getKeyFromChatItem } from "./utils/getKeyFromChatItem"; - -import { ChatItemWithGroupedNotifications } from "../../types"; - -type MessagesDialogueProps = { - chatItems: ChatItemWithGroupedNotifications[]; -}; - -export const ChatDialogue = ({ - chatItems, -}: MessagesDialogueProps): JSX.Element => { - const { t } = useTranslation(["chat"]); - const { chatListRef } = useChatDialogue(); - - const { shouldDisplayOnboardingAInstructions } = useOnboarding(); - - if (shouldDisplayOnboardingAInstructions) { - return ( -
-
- {chatItems.map((chatItem, index) => ( - - ))} -
-
- ); - } - - return ( -
- {chatItems.length === 0 ? ( -
- {t("ask", { ns: "chat" })} -
- ) : ( -
- {chatItems.map((chatItem, index) => ( - - ))} -
- )} -
- ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/styles/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/styles/index.ts deleted file mode 100644 index 5b7973eec..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/styles/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const chatItemContainerClassName = "flex flex-col gap-3"; - -export const chatDialogueContainerClassName = - "flex flex-col flex-1 overflow-y-auto mb-10"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/utils/getKeyFromChatItem.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/utils/getKeyFromChatItem.ts deleted file mode 100644 index 9a989c177..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/utils/getKeyFromChatItem.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChatItemWithGroupedNotifications } from "../../../types"; - -export const getKeyFromChatItem = ( - chatItem: ChatItemWithGroupedNotifications -): string => { - if (chatItem.item_type === "MESSAGE") { - return chatItem.body.message_id; - } else { - return chatItem.body[0].id; - } -}; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/index.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/index.ts deleted file mode 100644 index 51f13cb21..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ChatDialogue"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/types.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/types.ts deleted file mode 100644 index 70f685e3b..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ChatMessageItem, Notification } from "../../types"; - -export type ChatItemWithGroupedNotifications = - | ChatMessageItem - | { - item_type: "NOTIFICATION"; - body: Notification[]; - }; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/utils/getMergedChatMessagesWithDoneStatusNotificationsReduced.ts b/frontend/app/chat/[chatId]/components/ChatDialogueArea/utils/getMergedChatMessagesWithDoneStatusNotificationsReduced.ts deleted file mode 100644 index 6b49c9e69..000000000 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/utils/getMergedChatMessagesWithDoneStatusNotificationsReduced.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - ChatItem, - ChatMessage, - ChatMessageItem, - Notification, - NotificationItem, -} from "../../../types"; -import { ChatItemWithGroupedNotifications } from "../types"; - -// Function to create a ChatMessageItem from a message -const createChatMessageItem = (message: ChatMessage): ChatMessageItem => ({ - item_type: "MESSAGE", - body: message, -}); - -// Function to create a NotificationItem from a notification -const createNotificationItem = ( - notification: Notification -): NotificationItem => ({ - item_type: "NOTIFICATION", - body: notification, -}); - -// Function to merge chat messages and notifications into a single array -const mergeChatMessagesAndNotifications = ( - messages: ChatMessage[], - notifications: Notification[] -): ChatItem[] => [ - ...messages.map(createChatMessageItem), - ...notifications.map(createNotificationItem), -]; - -// Function to compare two items by timestamp (message_time or datetime) -const compareItemsByTimestamp = (a: ChatItem, b: ChatItem): number => { - const timestampA = - a.item_type === "MESSAGE" ? a.body.message_time : a.body.datetime; - const timestampB = - b.item_type === "MESSAGE" ? b.body.message_time : b.body.datetime; - - return Date.parse(timestampA) - Date.parse(timestampB); -}; - -// Main function to get merged chat messages with reduced notifications using reduce -export const getMergedChatMessagesWithDoneStatusNotificationsReduced = ( - messages: ChatMessage[], - notifications: Notification[] -): ChatItemWithGroupedNotifications[] => { - const mergedChatItems = mergeChatMessagesAndNotifications( - messages, - notifications.filter((notification) => notification.status === "Done") - ); - mergedChatItems.sort(compareItemsByTimestamp); - - // Group notifications between messages - const groupedChatItemsByNotifications = mergedChatItems.reduce( - (result, item) => { - if (item.item_type === "MESSAGE") { - result.push(item); - } else { - const lastItem = result[result.length - 1]; - if (lastItem !== undefined && lastItem.item_type === "NOTIFICATION") { - lastItem.body.push(item.body); - } else { - result.push({ - item_type: "NOTIFICATION", - body: [item.body], - }); - } - } - - return result; - }, - [] as ChatItemWithGroupedNotifications[] - ); - - return groupedChatItemsByNotifications; -}; diff --git a/frontend/app/chat/[chatId]/components/index.ts b/frontend/app/chat/[chatId]/components/index.ts deleted file mode 100644 index bc132561e..000000000 --- a/frontend/app/chat/[chatId]/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ChatDialogueArea/components/ChatDialogue"; diff --git a/frontend/app/chat/[chatId]/hooks/useChat.ts b/frontend/app/chat/[chatId]/hooks/useChat.ts deleted file mode 100644 index 5b786542b..000000000 --- a/frontend/app/chat/[chatId]/hooks/useChat.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* eslint-disable max-lines */ -import { useQueryClient } from "@tanstack/react-query"; -import { AxiosError } from "axios"; -import { useParams, useRouter } from "next/navigation"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { CHATS_DATA_KEY } from "@/lib/api/chat/config"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { useChatContext } from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { getChatNameFromQuestion } from "@/lib/helpers/getChatNameFromQuestion"; -import { useToast } from "@/lib/hooks"; -import { useOnboarding } from "@/lib/hooks/useOnboarding"; -import { useOnboardingTracker } from "@/lib/hooks/useOnboardingTracker"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -import { useLocalStorageChatConfig } from "./useLocalStorageChatConfig"; -import { useQuestion } from "./useQuestion"; - -import { ChatQuestion } from "../types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChat = () => { - const { track } = useEventTracking(); - const queryClient = useQueryClient(); - - const params = useParams(); - const [chatId, setChatId] = useState( - params?.chatId as string | undefined - ); - const { isOnboarding } = useOnboarding(); - const { trackOnboardingEvent } = useOnboardingTracker(); - const [generatingAnswer, setGeneratingAnswer] = useState(false); - const router = useRouter(); - const { messages } = useChatContext(); - const { currentBrain, currentPromptId, currentBrainId } = useBrainContext(); - const { publish } = useToast(); - const { createChat } = useChatApi(); - const { - chatConfig: { model, maxTokens, temperature }, - } = useLocalStorageChatConfig(); - const { isVisible } = useSearchModalContext(); - const { getUserCredits } = useUserApi(); - const { setRemainingCredits } = useUserSettingsContext(); - - const { addStreamQuestion } = useQuestion(); - const { t } = useTranslation(["chat"]); - - const addQuestion = async (question: string, callback?: () => void) => { - if (question === "") { - publish({ - variant: "danger", - text: t("ask"), - }); - - return; - } - - try { - setGeneratingAnswer(true); - - let currentChatId = chatId; - - //if chatId is not set, create a new chat. Chat name is from the first question - if (currentChatId === undefined || isVisible) { - const chat = await createChat(getChatNameFromQuestion(question)); - currentChatId = chat.chat_id; - setChatId(currentChatId); - router.push(`/chat/${currentChatId}`); - void queryClient.invalidateQueries({ - queryKey: [CHATS_DATA_KEY], - }); - } - - if (isOnboarding) { - void trackOnboardingEvent("QUESTION_ASKED", { - brainId: currentBrainId, - promptId: currentPromptId, - }); - } else { - void track("QUESTION_ASKED", { - brainId: currentBrainId, - promptId: currentPromptId, - }); - } - - const chatQuestion: ChatQuestion = { - model, // eslint-disable-line @typescript-eslint/no-unsafe-assignment - question, - temperature: temperature, - max_tokens: maxTokens, - brain_id: currentBrain?.id, - prompt_id: currentPromptId ?? undefined, - }; - - callback?.(); - await addStreamQuestion(currentChatId, chatQuestion); - void (async () => { - const res = await getUserCredits(); - setRemainingCredits(res); - })(); - } catch (error) { - console.error({ error }); - - if ((error as AxiosError).response?.status === 429) { - publish({ - variant: "danger", - text: t("limit_reached", { ns: "chat" }), - }); - - return; - } - - publish({ - variant: "danger", - text: t("error_occurred", { ns: "chat" }), - }); - } finally { - setGeneratingAnswer(false); - } - }; - - return { - messages, - addQuestion, - generatingAnswer, - chatId, - }; -}; diff --git a/frontend/app/chat/[chatId]/hooks/useChatNotificationsSync.ts b/frontend/app/chat/[chatId]/hooks/useChatNotificationsSync.ts deleted file mode 100644 index 294b46a3c..000000000 --- a/frontend/app/chat/[chatId]/hooks/useChatNotificationsSync.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { useParams } from "next/navigation"; -import { useEffect } from "react"; - -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useNotificationApi } from "@/lib/api/notification/useNotificationApi"; -import { useChatContext } from "@/lib/context"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; - -import { getChatNotificationsQueryKey } from "../utils/getChatNotificationsQueryKey"; -import { getMessagesFromChatItems } from "../utils/getMessagesFromChatItems"; -import { getNotificationsFromChatItems } from "../utils/getNotificationsFromChatItems"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatNotificationsSync = () => { - const { setMessages, setNotifications, notifications } = useChatContext(); - const { getChatItems } = useChatApi(); - const { getChatNotifications } = useNotificationApi(); - const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); - const params = useParams(); - const chatId = params?.chatId as string | undefined; - - const chatNotificationsQueryKey = getChatNotificationsQueryKey(chatId ?? ""); - const { data: fetchedNotifications = [] } = useQuery({ - queryKey: [chatNotificationsQueryKey], - enabled: notifications.length > 0, - queryFn: () => { - if (chatId === undefined) { - return []; - } - - return getChatNotifications(chatId); - }, - refetchInterval: () => { - if (notifications.length === 0) { - return false; - } - const hasAPendingNotification = notifications.find( - (item) => item.status === "Pending" - ); - - if (hasAPendingNotification) { - return 2_000; // in ms - } - - return false; - }, - }); - - useEffect(() => { - if (fetchedNotifications.length === 0) { - return; - } - setNotifications(fetchedNotifications); - }, [fetchedNotifications]); - - useEffect(() => { - setShouldDisplayFeedCard(false); - const fetchHistory = async () => { - if (chatId === undefined) { - setMessages([]); - setNotifications([]); - - return; - } - const chatItems = await getChatItems(chatId); - const messagesFromChatItems = getMessagesFromChatItems(chatItems); - if ( - messagesFromChatItems.length > 1 || - (messagesFromChatItems[0] && messagesFromChatItems[0].assistant !== "") - ) { - setMessages(messagesFromChatItems); - setNotifications(getNotificationsFromChatItems(chatItems)); - } - }; - void fetchHistory(); - }, [chatId]); -}; diff --git a/frontend/app/chat/[chatId]/hooks/useChatsList.ts b/frontend/app/chat/[chatId]/hooks/useChatsList.ts deleted file mode 100644 index 0e9822780..000000000 --- a/frontend/app/chat/[chatId]/hooks/useChatsList.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { useEffect } from "react"; -import { useTranslation } from "react-i18next"; - -import { CHATS_DATA_KEY } from "@/lib/api/chat/config"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useChatsContext } from "@/lib/context/ChatsProvider/hooks/useChatsContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatsList = () => { - const { t } = useTranslation(["chat"]); - - const { setAllChats, setIsLoading } = useChatsContext(); - const { publish } = useToast(); - const { getChats } = useChatApi(); - const { session } = useSupabase(); - - const fetchAllChats = async () => { - if (session) { - try { - const response = await getChats(); - - return response.reverse(); - } catch (error) { - console.error(error); - publish({ - variant: "danger", - text: t("errorFetching", { ns: "chat" }), - }); - } - } - }; - - const { data: chats, isLoading } = useQuery({ - queryKey: [CHATS_DATA_KEY], - queryFn: fetchAllChats, - }); - - useEffect(() => { - setAllChats(chats ?? []); - }, [chats]); - - useEffect(() => { - setIsLoading(isLoading); - }, [isLoading]); -}; diff --git a/frontend/app/chat/[chatId]/hooks/useHandleStream.ts b/frontend/app/chat/[chatId]/hooks/useHandleStream.ts deleted file mode 100644 index 2d0bb5bff..000000000 --- a/frontend/app/chat/[chatId]/hooks/useHandleStream.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useChatContext } from "@/lib/context"; - -import { ChatMessage } from "../types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useHandleStream = () => { - const { updateStreamingHistory } = useChatContext(); - - const handleStream = async ( - reader: ReadableStreamDefaultReader, - onFirstChunk: () => void - ): Promise => { - const decoder = new TextDecoder("utf-8"); - let isFirstChunk = true; - let incompleteData = ""; - - const handleStreamRecursively = async () => { - const { done, value } = await reader.read(); - - if (done) { - if (incompleteData !== "") { - // Try to parse any remaining incomplete data - - try { - const parsedData = JSON.parse(incompleteData) as ChatMessage; - updateStreamingHistory(parsedData); - } catch (e) { - console.error("Error parsing incomplete data", e); - } - } - - return; - } - - if (isFirstChunk) { - isFirstChunk = false; - onFirstChunk(); - } - - // Concatenate incomplete data with new chunk - const rawData = incompleteData + decoder.decode(value, { stream: true }); - - const dataStrings = rawData.trim().split("data: ").filter(Boolean); - - dataStrings.forEach((data, index, array) => { - if (index === array.length - 1 && !data.endsWith("\n")) { - // Last item and does not end with a newline, save as incomplete - incompleteData = data; - - return; - } - - try { - const parsedData = JSON.parse(data) as ChatMessage; - updateStreamingHistory(parsedData); - } catch (e) { - console.error("Error parsing data string", e); - } - }); - - await handleStreamRecursively(); - }; - - await handleStreamRecursively(); - }; - - return { - handleStream, - }; -}; diff --git a/frontend/app/chat/[chatId]/hooks/useLocalStorageChatConfig.ts b/frontend/app/chat/[chatId]/hooks/useLocalStorageChatConfig.ts deleted file mode 100644 index ae4d0f5ae..000000000 --- a/frontend/app/chat/[chatId]/hooks/useLocalStorageChatConfig.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getChatsConfigFromLocalStorage } from "@/lib/api/chat/chat.local"; -import { useUserData } from "@/lib/hooks/useUserData"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useLocalStorageChatConfig = () => { - const { userData } = useUserData(); - - const chatConfig = getChatsConfigFromLocalStorage(); - - const model = (userData?.models ?? []).includes(chatConfig?.model ?? "") - ? chatConfig?.model - : undefined; - - return { - chatConfig: { - model: model, - temperature: chatConfig?.temperature, - maxTokens: chatConfig?.maxTokens, - }, - }; -}; diff --git a/frontend/app/chat/[chatId]/hooks/useQuestion.ts b/frontend/app/chat/[chatId]/hooks/useQuestion.ts deleted file mode 100644 index 063e7e70e..000000000 --- a/frontend/app/chat/[chatId]/hooks/useQuestion.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { useChatContext } from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useFetch, useToast } from "@/lib/hooks"; - -import { useHandleStream } from "./useHandleStream"; - -import { ChatQuestion } from "../types"; -import { generatePlaceHolderMessage } from "../utils/generatePlaceHolderMessage"; - -interface UseChatService { - addStreamQuestion: ( - chatId: string, - chatQuestion: ChatQuestion - ) => Promise; -} - -export const useQuestion = (): UseChatService => { - const { fetchInstance } = useFetch(); - const { currentBrain } = useBrainContext(); - - const { t } = useTranslation(["chat"]); - const { publish } = useToast(); - const { handleStream } = useHandleStream(); - const { removeMessage, updateStreamingHistory } = useChatContext(); - - const handleFetchError = async (response: Response) => { - if (response.status === 429) { - publish({ - variant: "danger", - text: t("tooManyRequests", { ns: "chat" }), - }); - - return; - } - - const errorMessage = (await response.json()) as { detail: string }; - publish({ - variant: "danger", - text: errorMessage.detail, - }); - }; - - const addStreamQuestion = async ( - chatId: string, - chatQuestion: ChatQuestion - ): Promise => { - const headers = { - "Content-Type": "application/json", - Accept: "text/event-stream", - }; - - const placeHolderMessage = generatePlaceHolderMessage({ - user_message: chatQuestion.question ?? "", - chat_id: chatId, - }); - updateStreamingHistory(placeHolderMessage); - - const body = JSON.stringify(chatQuestion); - - try { - let url = `/chat/${chatId}/question/stream`; - if (currentBrain?.id) { - url += `?brain_id=${currentBrain.id}`; - } - const response = await fetchInstance.post(url, body, headers); - if (!response.ok) { - void handleFetchError(response); - - return; - } - - if (response.body === null) { - throw new Error(t("resposeBodyNull", { ns: "chat" })); - } - - await handleStream(response.body.getReader(), () => - removeMessage(placeHolderMessage.message_id) - ); - } catch (error) { - publish({ - variant: "danger", - text: String(error), - }); - } - }; - - return { - addStreamQuestion, - }; -}; diff --git a/frontend/app/chat/[chatId]/page.module.scss b/frontend/app/chat/[chatId]/page.module.scss deleted file mode 100644 index c9af8c139..000000000 --- a/frontend/app/chat/[chatId]/page.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.main_container { - display: flex; - flex-direction: column; - width: 100%; - - .chat_page_container { - display: flex; - flex: 1 1 0%; - background-color: var(--background-0); - padding-block: Spacings.$spacing06; - padding-inline: Spacings.$spacing09; - display: flex; - gap: Spacings.$spacing09; - overflow: hidden; - - .sources_wrapper { - height: 100%; - width: 0; - animation: expand 0.2s forwards; - - @keyframes expand { - to { - width: 30%; - } - } - } - } -} diff --git a/frontend/app/chat/[chatId]/page.tsx b/frontend/app/chat/[chatId]/page.tsx deleted file mode 100644 index af1e8ee4d..000000000 --- a/frontend/app/chat/[chatId]/page.tsx +++ /dev/null @@ -1,103 +0,0 @@ -"use client"; - -import { useEffect } from "react"; - -import { AddBrainModal } from "@/lib/components/AddBrainModal"; -import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider"; -import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; -import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; -import { useChatContext } from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useCustomDropzone } from "@/lib/hooks/useDropzone"; -import { ButtonType } from "@/lib/types/QuivrButton"; -import { cn } from "@/lib/utils"; - -import { ActionsBar } from "./components/ActionsBar"; -import { ChatDialogueArea } from "./components/ChatDialogueArea/ChatDialogue"; -import { useChatNotificationsSync } from "./hooks/useChatNotificationsSync"; -import styles from "./page.module.scss"; - -const SelectedChatPage = (): JSX.Element => { - const { getRootProps } = useCustomDropzone(); - - const { setShouldDisplayFeedCard, shouldDisplayFeedCard } = - useKnowledgeToFeedContext(); - const { setIsBrainCreationModalOpened } = useBrainCreationContext(); - - const { currentBrain, setCurrentBrainId } = useBrainContext(); - const { messages } = useChatContext(); - - useChatNotificationsSync(); - - const buttons: ButtonType[] = [ - { - label: "Create brain", - color: "primary", - onClick: () => { - setIsBrainCreationModalOpened(true); - }, - iconName: "brain", - }, - { - label: "Add knowledge", - color: "primary", - onClick: () => { - setShouldDisplayFeedCard(true); - }, - iconName: "uploadFile", - hidden: !currentBrain?.max_files, - }, - { - label: "Manage current brain", - color: "primary", - onClick: () => { - window.location.href = `/studio/${currentBrain?.id}`; - }, - iconName: "edit", - }, - ]; - - useEffect(() => { - if (!currentBrain && messages.length > 0) { - const lastMessage = messages[messages.length - 1]; - setCurrentBrainId( - lastMessage.brain_id - ? lastMessage.brain_id - : lastMessage.metadata?.metadata_model?.brain_id ?? null - ); - } - }, [messages]); - - return ( -
-
- -
-
-
-
-
- -
- -
-
- - -
-
- ); -}; - -export default SelectedChatPage; diff --git a/frontend/app/chat/[chatId]/types/index.ts b/frontend/app/chat/[chatId]/types/index.ts deleted file mode 100644 index 6d38965b5..000000000 --- a/frontend/app/chat/[chatId]/types/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { UUID } from "crypto"; - -import { Source } from "@/lib/types/MessageMetadata"; - -export type ChatQuestion = { - model?: string; - question?: string; - temperature?: number; - max_tokens?: number; - brain_id?: string; - prompt_id?: string; -}; -export type ChatMessage = { - chat_id: string; - message_id: string; - user_message: string; - assistant: string; - message_time: string; - prompt_title?: string; - brain_name?: string; - brain_id?: UUID; - metadata?: { - sources?: Source[]; - thoughts?: string; - followup_questions?: string[]; - snippet_color?: string; - snippet_emoji?: string; - metadata_model?: { - display_name: string; - image_url: string; - brain_id: UUID; - }; - }; - thumbs?: boolean; -}; - -type NotificationStatus = "Pending" | "Done"; - -export type Notification = { - id: string; - datetime: string; - chat_id?: string | null; - message?: string | null; - action: string; - status: NotificationStatus; -}; - -export type ChatMessageItem = { - item_type: "MESSAGE"; - body: ChatMessage; -}; - -export type NotificationItem = { - item_type: "NOTIFICATION"; - body: Notification; -}; - -export type ChatItem = ChatMessageItem | NotificationItem; - -export type ChatEntity = { - chat_id: UUID; - user_id: string; - creation_time: string; - chat_name: string; -}; diff --git a/frontend/app/chat/[chatId]/utils/generatePlaceHolderMessage.ts b/frontend/app/chat/[chatId]/utils/generatePlaceHolderMessage.ts deleted file mode 100644 index 601ade7d0..000000000 --- a/frontend/app/chat/[chatId]/utils/generatePlaceHolderMessage.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ChatMessage } from "../types"; - -type GeneratePlaceHolderMessageProps = { - user_message: string; - chat_id: string; -}; - -export const generatePlaceHolderMessage = ({ - user_message, - chat_id, -}: GeneratePlaceHolderMessageProps): ChatMessage => { - return { - message_id: new Date().getTime().toString(), - message_time: new Date( - new Date().setDate(new Date().getDate() + 1) - ).toISOString(), - assistant: "🧠", - chat_id, - user_message, - }; -}; diff --git a/frontend/app/chat/[chatId]/utils/getChatNotificationsQueryKey.ts b/frontend/app/chat/[chatId]/utils/getChatNotificationsQueryKey.ts deleted file mode 100644 index 36869479e..000000000 --- a/frontend/app/chat/[chatId]/utils/getChatNotificationsQueryKey.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const getChatNotificationsQueryKey = (chatId: string): string => - `notifications-${chatId}`; diff --git a/frontend/app/chat/[chatId]/utils/getMessagesFromChatItems.ts b/frontend/app/chat/[chatId]/utils/getMessagesFromChatItems.ts deleted file mode 100644 index 54f90e19f..000000000 --- a/frontend/app/chat/[chatId]/utils/getMessagesFromChatItems.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChatItem, ChatMessage } from "../types"; - -export const getMessagesFromChatItems = ( - chatItems: ChatItem[] -): ChatMessage[] => { - const messages = chatItems - .filter((item) => item.item_type === "MESSAGE") - .map((item) => item.body as ChatMessage); - - return messages; -}; diff --git a/frontend/app/chat/[chatId]/utils/getNotificationsFromChatItems.ts b/frontend/app/chat/[chatId]/utils/getNotificationsFromChatItems.ts deleted file mode 100644 index 26bdf48ec..000000000 --- a/frontend/app/chat/[chatId]/utils/getNotificationsFromChatItems.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChatItem, Notification } from "../types"; - -export const getNotificationsFromChatItems = ( - chatItems: ChatItem[] -): Notification[] => { - const messages = chatItems - .filter((item) => item.item_type === "NOTIFICATION") - .map((item) => item.body as Notification); - - return messages; -}; diff --git a/frontend/app/chat/layout.tsx b/frontend/app/chat/layout.tsx deleted file mode 100644 index a745667dd..000000000 --- a/frontend/app/chat/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; -import { usePathname, useRouter } from "next/navigation"; -import { ReactNode, useEffect, useState } from "react"; - -interface LayoutProps { - children?: ReactNode; -} - -const Layout = ({ children }: LayoutProps): JSX.Element => { - const pathname = usePathname(); - const router = useRouter(); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - if (pathname === "/chat") { - router.push("/search"); - } else { - setIsLoading(false); - } - }, [pathname, router]); - - if (isLoading) { - return <>; - } - - return ( -
- {children} -
- ); -}; - -export default Layout; diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx deleted file mode 100644 index 24e32a55d..000000000 --- a/frontend/app/chat/page.tsx +++ /dev/null @@ -1,4 +0,0 @@ -"use client"; -import ChatPage from "./[chatId]/page"; - -export default ChatPage; diff --git a/frontend/app/colors.css b/frontend/app/colors.css deleted file mode 100644 index 86dcc7d70..000000000 --- a/frontend/app/colors.css +++ /dev/null @@ -1,60 +0,0 @@ -:root { - --white-0: #fcfcfc; - --white-1: #f8f8f8; - --white-2: #f4f4f4; - - /* Black */ - --black-0: #1f1f1f; - --black-1: #252525; - --black-2: #2b2b2b; - --black-3: #313131; - --black-4: #373737; - --black-5: #3d3d3d; - --black-6: #434343; - --black-7: #494949; - - /* Grey */ - --grey-O: #707070; - --grey-1: #818080; - --grey-2: #c8c8c8; - --grey-3: #cbcbcb; - --grey-4: #e7e9ec; - --grey-5: #d3d3d3; - --grey-6: #f5f5f5; - - /* Primary */ - --primary-0: #6142d4; - --primary-1: #d0c6f2; - --primary-2: #f5f3fd; - - /* Accent */ - --accent: #13abba; - - /* Gold */ - --gold: #b8860b; - --gold-lightest: #f0ebdd; - - /* Error */ - --dangerous-dark: #e30c17; - --dangerous: #9b373c; - --dangerous-lightest: #eedddd; - - /* Warning */ - --warning: #c77d33; - --warning-lightest: #f2e9e0; - - /* Success */ - --success: #47a455; - --success-lightest: #d0edd4; - - /* Code */ - --code-yellow: #e6db74; - --code-grey: #f8f8f2; - --code-green: #a6e22e; - --code-purple: #ae81ff; - --code-red: #f92672; - --code-dark-grey: #f8f8f2; - --code-very-dark-grey: #75715e; - --code-blue: #66d9ef; - --code-orange: #fd971f; -} diff --git a/frontend/app/global-error.jsx b/frontend/app/global-error.jsx deleted file mode 100644 index f63e01a60..000000000 --- a/frontend/app/global-error.jsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import * as Sentry from "@sentry/nextjs"; -import Error from "next/error"; -import { useEffect } from "react"; - -const GlobalError = ({ error }) => { - useEffect(() => { - Sentry.captureException(error); - }, [error]); - - return ( - - - - - - ); -}; - -export default GlobalError; diff --git a/frontend/app/globals.css b/frontend/app/globals.css deleted file mode 100644 index fac648532..000000000 --- a/frontend/app/globals.css +++ /dev/null @@ -1,232 +0,0 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; -@import "./colors.css"; - -main { - @apply max-w-screen-xl mx-auto flex flex-col; -} - -header, -section { - @apply px-5 md:px-10; -} - -a { - @apply hover:text-primary dark:hover:text-gray-200 transition-colors; -} - -div:focus { - @apply outline-none; -} - -@layer utilities { - .scrollbar::-webkit-scrollbar { - @apply w-2 h-2; - } - - .scrollbar::-webkit-scrollbar-track { - border-radius: 10px; - background-color: var(--background-0); - } - - .scrollbar::-webkit-scrollbar-thumb { - background-color: var(--background-3); - border-radius: 4px; - max-height: 30px; - } - - .scrollbar::-webkit-scrollbar-thumb:hover { - background-color: var(--background-2); - } - - .h-fill-available { - /* fixes the h-[100vh] issue on mobile */ - /* like height: -webkit-fill-available; but for any device (android and iphone) */ - @apply h-[100vh] supports-[height:100cqh]:h-[100cqh] supports-[height:100svh]:h-[100svh]; - } -} - -:root { - /* Backgrounds */ - --background-0: var(--white-0); - --background-1: var(--white-1); - --background-2: var(--grey-6); - --background-3: var(--grey-4); - --background-4: var(--grey-0); - --background-5: var(--black-0); - --background-primary-0: var(--primary-2); - --background-primary-1: var(--primary-1); - --background-blur: rgba(0, 0, 0, 0.9); - --background-success: var(--success-lightest); - --background-error: var(--dangerous-lightest); - --background-pending: var(--background-3); - - /* Borders */ - --border-0: var(--grey-5); - --border-1: var(--grey-4); - --border-2: var(--grey-2); - - /* Icons */ - --icon-0: var(--white-0); - --icon-1: var(--grey-2); - --icon-2: var(--grey-0); - --icon-3: var(--black-0); - - /* Text */ - --text-0: var(--white-0); - --text-1: var(--grey-2); - --text-2: var(--grey-0); - --text-3: var(--black-0); - --text-4: var(--grey-1); - - /* Box Shadow */ - --box-shadow: rgba(0, 0, 0, 0.25); -} - -body.dark_mode { - /* Backgrounds */ - --background-0: var(--black-1); - --background-1: var(--black-0); - --background-2: var(--black-2); - --background-3: var(--black-3); - --background-4: var(--black-4); - --background-5: var(--black-5); - --background-primary-0: var(--black-5); - --background-primary-1: var(--black-5); - --background-opposite: var(--white-0); - --background-blur: rgba(0, 0, 0, 0.9); - --background-success: var(--black-5); - --background-error: var(--black-5); - --background-pending: var(--black-5); - - /* Borders */ - --border-0: var(--black-5); - --border-1: var(--black-6); - --border-2: var(--black-7); - - /* Icons */ - --icon-0: var(--black-0); - --icon-1: var(--grey-0); - --icon-2: var(--grey-2); - --icon-3: var(--white-0); - - /* Text */ - --text-0: var(--black-0); - --text-1: var(--grey-0); - --text-2: var(--grey-2); - --text-3: var(--white-2); - --text-4: var(--grey-1); - - /* Box Shadow */ - --box-shadow: rgba(255, 255, 255, 0.25); -} - -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: var(--code-very-dark-grey); - font-style: italic; -} - -.token.punctuation { - color: var(--code-dark-grey); -} - -.token.property, -.token.tag, -.token.constant, -.token.symbol, -.token.deleted { - color: var(--code-red); -} - -.token.boolean, -.token.number { - color: var(--code-purple); -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: var(--code-green); -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: var(--code-grey); -} - -.token.atrule, -.token.attr-value, -.token.function, -.token.class-name { - color: var(--code-yellow); -} - -.token.keyword { - color: var(--code-blue); -} - -.token.regex, -.token.important { - color: var(--code-orange); -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.EmojiPickerReact { - --epr-emoji-size: 20px !important; - --epr-hover-bg-color: var(--primary-1) !important; - --epr-category-navigation-button-size: 24px !important; - --epr-category-label-bg-color: var(--background-0) !important; - --epr-header-padding: 8px 16px !important; -} - -.EmojiPickerReact.epr-dark-theme { - --epr-emoji-size: 20px !important; - --epr-hover-bg-color: var(--primary-1) !important; - --epr-category-navigation-button-size: 24px !important; - --epr-category-label-bg-color: var(--background-0) !important; - --epr-header-padding: 8px 16px !important; -} - -.react-colorful { - width: 250px !important; - height: 300px !important; - border: none !important; -} - -@media (min-width: 768px) { - .react-colorful { - width: 375px !important; - } -} - -.react-colorful__last-control { - border-radius: 0 !important; -} - -.react-colorful__saturation { - border-radius: 0 !important; -} diff --git a/frontend/app/invitation/[brainId]/hooks/useInvitation.ts b/frontend/app/invitation/[brainId]/hooks/useInvitation.ts deleted file mode 100644 index bf68e855e..000000000 --- a/frontend/app/invitation/[brainId]/hooks/useInvitation.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable max-lines */ -"use client"; -import { AxiosResponse, isAxiosError } from "axios"; -import { UUID } from "crypto"; -import { useParams, useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; -import { useSubscriptionApi } from "@/lib/api/subscription/useSubscriptionApi"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useOnboardingContext } from "@/lib/context/OnboardingProvider/hooks/useOnboardingContext"; -import { useToast } from "@/lib/hooks"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useInvitation = () => { - const { t } = useTranslation(["brain", "invitation"]); - const params = useParams(); - const brainId = params?.brainId as UUID | undefined; - const [isLoading, setIsLoading] = useState(false); - const [brainName, setBrainName] = useState(""); - const [role, setRole] = useState(); - const [isProcessingRequest, setIsProcessingRequest] = useState(false); - const { setIsBrainCreated } = useOnboardingContext(); - - const { publish } = useToast(); - const { track } = useEventTracking(); - const { getInvitation, acceptInvitation, declineInvitation } = - useSubscriptionApi(); - - if (brainId === undefined) { - throw new Error(t("brainUndefined", { ns: "brain" })); - } - - const { fetchAllBrains, setCurrentBrainId } = useBrainContext(); - const router = useRouter(); - - useEffect(() => { - setIsLoading(true); - - const checkInvitationValidity = async () => { - try { - const { name, role: assignedRole } = await getInvitation(brainId); - setBrainName(name); - setRole(assignedRole); - } catch (error) { - if (isAxiosError(error) && error.response?.status === 404) { - publish({ - variant: "warning", - text: t("invitationNotFound", { ns: "invitation" }), - }); - } else { - publish({ - variant: "danger", - text: t("errorCheckingInvitation", { ns: "invitation" }), - }); - } - router.push("/"); - } finally { - setIsLoading(false); - } - }; - void checkInvitationValidity(); - }, [brainId]); - - const handleAccept = async () => { - setIsBrainCreated(true); - setIsProcessingRequest(true); - try { - await acceptInvitation(brainId); - void track("INVITATION_ACCEPTED"); - - await fetchAllBrains(); - publish({ - variant: "success", - text: t("accept", { ns: "invitation" }), - }); - setCurrentBrainId(brainId); - } catch (error) { - if (isAxiosError(error) && error.response?.data !== undefined) { - publish({ - variant: "danger", - text: ( - error.response as AxiosResponse<{ - detail: string; - }> - ).data.detail, - }); - } else { - console.error("Error calling the API:", error); - publish({ - variant: "danger", - text: t("errorAccepting", { ns: "invitation" }), - }); - } - } finally { - setIsProcessingRequest(false); - void router.push("/chat"); - } - }; - - const handleDecline = async () => { - setIsProcessingRequest(true); - try { - await declineInvitation(brainId); - publish({ - variant: "success", - text: t("declined", { ns: "invitation" }), - }); - void track("INVITATION_DECLINED"); - } catch (error) { - if (isAxiosError(error) && error.response?.data !== undefined) { - publish({ - variant: "danger", - text: ( - error.response as AxiosResponse<{ - detail: string; - }> - ).data.detail, - }); - } else { - publish({ - variant: "danger", - text: t("errorDeclining", { ns: "invitation" }), - }); - } - } finally { - setIsProcessingRequest(false); - void router.push("/chat"); - } - }; - - return { - handleAccept, - handleDecline, - brainName, - role, - isLoading, - isProcessingRequest, - }; -}; diff --git a/frontend/app/invitation/[brainId]/page.tsx b/frontend/app/invitation/[brainId]/page.tsx deleted file mode 100644 index 50d0ffedf..000000000 --- a/frontend/app/invitation/[brainId]/page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { useTranslation } from "react-i18next"; - -import Button from "@/lib/components/ui/Button"; -import PageHeading from "@/lib/components/ui/PageHeading"; -import Spinner from "@/lib/components/ui/Spinner"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; - -import { useInvitation } from "./hooks/useInvitation"; - -const InvitationPage = (): JSX.Element => { - const { t } = useTranslation("invitation"); - const { - handleAccept, - isProcessingRequest, - handleDecline, - isLoading, - brainName, - role, - } = useInvitation(); - const { session } = useSupabase(); - - if (isLoading) { - return ; - } - - if (session?.user === undefined) { - redirectToLogin(); - } - - if (role === undefined) { - // This should never happen - // It is a way to prevent the page from crashing when invitation is invalid instead of throwing an error - // The user will be redirected to the home page (handled in the useInvitation hook) - return
; - } - - return ( -
- - {isProcessingRequest ? ( -
- -

- {t("processingRequest", { ns: "invitation" })} -

-
- ) : ( -
- - -
- )} -
- ); -}; - -export default InvitationPage; diff --git a/frontend/app/layout.module.scss b/frontend/app/layout.module.scss deleted file mode 100644 index 965563b8e..000000000 --- a/frontend/app/layout.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.body { - color: var(--text-3); - background-color: var(--background-0); - display: flex; - flex-direction: column; - width: 100%; - height: 100vh; -} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx deleted file mode 100644 index efc504e2d..000000000 --- a/frontend/app/layout.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { createServerComponentSupabaseClient } from "@supabase/auth-helpers-nextjs"; -import { Analytics as VercelAnalytics } from "@vercel/analytics/react"; -import { cookies, headers } from "next/headers"; - -import { ToastProvider } from "@/lib/components/ui/Toast"; -import { SupabaseProvider } from "@/lib/context/SupabaseProvider"; - -import { App } from "./App"; -import "./globals.css"; -import styles from "./layout.module.scss"; - -export const metadata = { - title: "Quivr - Get a Second Brain with Generative AI", - description: - "Quivr is your second brain in the cloud, designed to easily store and retrieve unstructured information.", -}; - -const RootLayout = async ({ - children, -}: { - children: React.ReactNode; -}): Promise => { - const supabase = createServerComponentSupabaseClient({ - headers, - cookies, - }); - - const { - data: { session }, - } = await supabase.auth.getSession(); - - return ( - - - - - {children} - - - - - - ); -}; - -export default RootLayout; diff --git a/frontend/app/not-found.tsx b/frontend/app/not-found.tsx deleted file mode 100644 index 0741a5f80..000000000 --- a/frontend/app/not-found.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import Link from "next/link"; - -const NotFound = (): JSX.Element => { - return ( -
-

Not found – 404!

-
- Go back to Home -
-
- ); -}; - -export default NotFound; diff --git a/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.module.scss b/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.module.scss deleted file mode 100644 index 339781715..000000000 --- a/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.assistant_tab_wrapper { - display: flex; - flex-direction: column; - align-items: center; - gap: Spacings.$spacing05; - border-radius: Radius.$normal; - border: 1px solid var(--border-0); - padding: Spacings.$spacing05; - width: 250px; - cursor: pointer; - height: 100%; - - &.disabled { - pointer-events: none; - opacity: 0.3; - } - - .header { - display: flex; - align-self: flex-start; - align-items: center; - gap: Spacings.$spacing03; - - .title { - @include Typography.H3; - } - } - - .description { - font-size: Typography.$small; - font-style: italic; - } - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.tsx b/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.tsx deleted file mode 100644 index ffc2e45f5..000000000 --- a/frontend/app/quality-assistant/AssistantTab/AssistantCard/AssistantCard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -"use client"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import styles from "./AssistantCard.module.scss"; - -import { Assistant } from "../../types/assistant"; - -interface AssistantCardProps { - assistant: Assistant; -} - -const AssistantCard = ({ assistant }: AssistantCardProps): JSX.Element => { - return ( -
-
- - {assistant.name} -
- {assistant.description} -
- ); -}; - -export default AssistantCard; diff --git a/frontend/app/quality-assistant/AssistantTab/AssistantTab.module.scss b/frontend/app/quality-assistant/AssistantTab/AssistantTab.module.scss deleted file mode 100644 index 663887883..000000000 --- a/frontend/app/quality-assistant/AssistantTab/AssistantTab.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.assistant_tab_wrapper { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - - .content_section { - display: flex; - flex-direction: column; - gap: Spacings.$spacing06; - - .title { - @include Typography.H2; - } - - .assistant_choice_wrapper { - display: flex; - gap: Spacings.$spacing05; - align-items: stretch; - flex-wrap: wrap; - } - } - - .form_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing06; - - .title { - @include Typography.H2; - } - - .file_inputs_wrapper { - display: flex; - justify-content: space-between; - width: 100%; - gap: Spacings.$spacing05; - - .file_input_wrapper { - width: 100%; - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - - .file_header { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - font-size: Typography.$small; - } - } - } - } - - .buttons_wrapper { - display: flex; - justify-content: space-between; - } -} diff --git a/frontend/app/quality-assistant/AssistantTab/AssistantTab.tsx b/frontend/app/quality-assistant/AssistantTab/AssistantTab.tsx deleted file mode 100644 index 75324db5f..000000000 --- a/frontend/app/quality-assistant/AssistantTab/AssistantTab.tsx +++ /dev/null @@ -1,267 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -import { useAssistants } from "@/lib/api/assistants/useAssistants"; -import { FileInput } from "@/lib/components/ui/FileInput/FileInput"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton"; - -import AssistantCard from "./AssistantCard/AssistantCard"; -import styles from "./AssistantTab.module.scss"; -import BooleansInputs from "./BooleansInputs/BooleansInputs"; -import SelectorsInputs from "./SelectorsInput/SelectorsInputs"; - -import { Assistant, ProcessAssistantData } from "../types/assistant"; - -export interface ProcessAssistantInput { - input: ProcessAssistantData; - files: File[]; -} - -interface AssistantTabProps { - setSelectedTab: (tab: string) => void; -} - -const FILE_TYPES = ["pdf", "docx", "doc", "txt", "xlsx", "xls"]; - -const useAssistantData = () => { - const [assistants, setAssistants] = useState([]); - const [assistantChoosed, setAssistantChoosed] = useState< - Assistant | undefined - >(undefined); - const { getAssistants } = useAssistants(); - - useEffect(() => { - void (async () => { - try { - const res = await getAssistants(); - setAssistants(res); - } catch (error) { - console.error(error); - } - })(); - }, []); - - return { assistants, assistantChoosed, setAssistantChoosed }; -}; - -const useFormStates = (assistantChoosed: Assistant | undefined) => { - const [booleanStates, setBooleanStates] = useState<{ - [key: string]: boolean | null; - }>({}); - const [selectTextStates, setSelectTextStates] = useState<{ - [key: string]: string | null; - }>({}); - const [fileStates, setFileStates] = useState<{ [key: string]: File }>({}); - const [isFormValid, setIsFormValid] = useState(false); - - useEffect(() => { - if (assistantChoosed?.inputs.booleans) { - const initialBooleanStates = assistantChoosed.inputs.booleans.reduce( - (acc, input) => ({ ...acc, [input.key]: false }), - {} - ); - setBooleanStates(initialBooleanStates); - } - if (assistantChoosed?.inputs.select_texts) { - const initialSelectTextStates = - assistantChoosed.inputs.select_texts.reduce( - (acc, input) => ({ ...acc, [input.key]: input.options[0] }), - {} - ); - setSelectTextStates(initialSelectTextStates); - } - }, [assistantChoosed]); - - return { - booleanStates, - setBooleanStates, - selectTextStates, - setSelectTextStates, - fileStates, - setFileStates, - isFormValid, - setIsFormValid, - }; -}; - -const validateForm = ( - assistantChoosed: Assistant | undefined, - booleanStates: { [x: string]: boolean | null }, - fileStates: { [x: string]: File | undefined }, - selectTextStates: { [x: string]: string | null } -) => { - if (!assistantChoosed) { - return false; - } - - const allBooleansSet = - assistantChoosed.inputs.booleans?.every( - (input) => - booleanStates[input.key] !== undefined && - booleanStates[input.key] !== null - ) ?? true; - - const allFilesSet = assistantChoosed.inputs.files.every( - (input) => fileStates[input.key] !== undefined - ); - - const allSelectTextsSet = - assistantChoosed.inputs.select_texts?.every( - (input) => - selectTextStates[input.key] !== undefined && - selectTextStates[input.key] !== null - ) ?? true; - - return allBooleansSet && allFilesSet && allSelectTextsSet; -}; - -const AssistantTab = ({ setSelectedTab }: AssistantTabProps): JSX.Element => { - const { assistants, assistantChoosed, setAssistantChoosed } = - useAssistantData(); - const { - booleanStates, - setBooleanStates, - selectTextStates, - setSelectTextStates, - fileStates, - setFileStates, - isFormValid, - setIsFormValid, - } = useFormStates(assistantChoosed); - const { processTask } = useAssistants(); - const [loading, setLoading] = useState(false); - - const handleFileChange = (key: string, file: File) => { - setFileStates((prevState) => ({ - ...prevState, - [key]: file, - })); - }; - - useEffect(() => { - setIsFormValid( - validateForm( - assistantChoosed, - booleanStates, - fileStates, - selectTextStates - ) - ); - }, [booleanStates, fileStates, selectTextStates, assistantChoosed]); - - const handleSubmit = async () => { - if (assistantChoosed) { - const processAssistantData: ProcessAssistantData = { - id: assistantChoosed.id, - name: assistantChoosed.name, - inputs: { - files: Object.keys(fileStates).map((key) => ({ - key, - value: fileStates[key].name, - })), - booleans: Object.keys(booleanStates).map((key) => ({ - key, - value: booleanStates[key] ?? null, - })), - select_texts: Object.keys(selectTextStates).map((key) => ({ - key, - value: selectTextStates[key], - })), - }, - }; - - const processAssistantInput: ProcessAssistantInput = { - input: processAssistantData, - files: Object.values(fileStates), - }; - - setLoading(true); - await processTask(processAssistantInput); - setSelectedTab("Process"); - setLoading(false); - } - }; - - const resetForm = () => { - setBooleanStates({}); - setSelectTextStates({}); - setFileStates({}); - setIsFormValid(false); - }; - - const handleBack = () => { - resetForm(); - setAssistantChoosed(undefined); - }; - - return ( -
- {!assistantChoosed ? ( -
- Choose an assistant -
- {assistants.map((assistant, index) => ( -
setAssistantChoosed(assistant)}> - -
- ))} -
-
- ) : ( -
- {assistantChoosed.name} -
- {assistantChoosed.inputs.files.map((input, index) => ( -
-
- - {input.key} -
- handleFileChange(input.key, file)} - acceptedFileTypes={FILE_TYPES} - /> -
- ))} -
- - -
- )} - {assistantChoosed && ( -
- handleBack()} - /> - -
- )} -
- ); -}; - -export default AssistantTab; diff --git a/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.module.scss b/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.module.scss deleted file mode 100644 index 8884e0266..000000000 --- a/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -@use "styles/Variables.module.scss"; - -.boolean_inputs_wrapper { - width: Variables.$assistantInputWidth; -} diff --git a/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.tsx b/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.tsx deleted file mode 100644 index b723808b7..000000000 --- a/frontend/app/quality-assistant/AssistantTab/BooleansInputs/BooleansInputs.tsx +++ /dev/null @@ -1,74 +0,0 @@ -"use client"; - -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; - -import styles from "./BooleansInputs.module.scss"; - -import { ConditionalInput } from "../../types/assistant"; - -interface BooleansInputsProps { - booleans: { key: string; description: string }[]; - conditionalInputs?: ConditionalInput[]; - booleanStates: { [key: string]: boolean | null }; - setBooleanStates: React.Dispatch< - React.SetStateAction<{ [key: string]: boolean | null }> - >; - selectTextStates: { [key: string]: string | null }; -} - -const BooleansInputs = ({ - booleans, - conditionalInputs, - booleanStates, - setBooleanStates, - selectTextStates, -}: BooleansInputsProps): JSX.Element => { - const handleCheckboxChange = (key: string, checked: boolean) => { - setBooleanStates((prevState: { [key: string]: boolean | null }) => ({ - ...prevState, - [key]: checked, - })); - }; - - const checkCondition = (conditionalInput: ConditionalInput): boolean => { - const { key, condition, value } = conditionalInput; - const targetValue = - booleanStates[key]?.toString() ?? selectTextStates[key] ?? ""; - - if (condition === "equals") { - return targetValue === value; - } else { - return targetValue !== value; - } - }; - - return ( -
- {booleans.map((input, index) => { - const shouldShow = !!conditionalInputs?.every((conditionalInput) => { - if (conditionalInput.conditional_key === input.key) { - return checkCondition(conditionalInput); - } - - return true; - }); - - if (!shouldShow) { - return null; - } - - return ( -
- handleCheckboxChange(input.key, checked)} - /> -
- ); - })} -
- ); -}; - -export default BooleansInputs; diff --git a/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.module.scss b/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.module.scss deleted file mode 100644 index 7bb633753..000000000 --- a/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -@use "styles/Variables.module.scss"; - -.select_texts_wrapper { - width: Variables.$assistantInputWidth; -} diff --git a/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.tsx b/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.tsx deleted file mode 100644 index 1a07f47d9..000000000 --- a/frontend/app/quality-assistant/AssistantTab/SelectorsInput/SelectorsInputs.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from "react"; - -import { SingleSelector } from "@/lib/components/ui/SingleSelector/SingleSelector"; - -import styles from "./SelectorsInputs.module.scss"; - -interface SelectorsInputsProps { - selectTexts: { key: string; options: string[] }[]; - selectTextStates: { [key: string]: string | null }; - setSelectTextStates: React.Dispatch< - React.SetStateAction<{ [key: string]: string | null }> - >; -} - -const SelectorsInputs = ({ - selectTexts, - selectTextStates, - setSelectTextStates, -}: SelectorsInputsProps): JSX.Element => { - const handleSelectTextChange = (key: string, value: string) => { - setSelectTextStates((prevState) => ({ - ...prevState, - [key]: value, - })); - }; - - return ( -
- {selectTexts.map((input, index) => ( -
- { - return { label: option, value: option }; - })} - onChange={(value) => handleSelectTextChange(input.key, value)} - selectedOption={{ - label: selectTextStates[input.key] ?? input.options[0], - value: selectTextStates[input.key] ?? input.options[0], - }} - /> -
- ))} -
- ); -}; - -export default SelectorsInputs; diff --git a/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.module.scss b/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.module.scss deleted file mode 100644 index 3f3c6fc39..000000000 --- a/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.module.scss +++ /dev/null @@ -1,193 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/Variables.module.scss"; - -.process_wrapper { - padding-inline: Spacings.$spacing06; - overflow: hidden; - display: flex; - gap: Spacings.$spacing02; - justify-content: space-between; - align-items: center; - border: 1px solid var(--border-0); - padding-block: Spacings.$spacing03; - position: relative; - overflow: visible; - font-size: Typography.$small; - border-bottom: none; - - &.last { - border-radius: 0 0 Radius.$normal Radius.$normal; - border-bottom: 1px solid var(--border-0); - } - - &.clickable { - cursor: pointer; - - &:hover { - background-color: var(--background-1); - } - } - - .left { - display: flex; - align-items: center; - gap: calc(Spacings.$spacing06 + 6px); - overflow: hidden; - - .left_fields { - display: flex; - align-items: center; - overflow: hidden; - - .assistant { - font-size: Typography.$small; - min-width: Variables.$menuSectionWidth; - max-width: Variables.$menuSectionWidth; - } - - .files { - font-size: Typography.$tiny; - color: var(--text-4); - overflow: hidden; - - .filename { - @include Typography.EllipsisOverflow; - } - } - } - } - - .right { - display: flex; - gap: Spacings.$spacing05; - align-items: center; - - .date { - font-size: Typography.$very_tiny; - width: 150px; - display: flex; - align-items: center; - justify-content: center; - @include Typography.EllipsisOverflow; - } - - .status { - width: 100px; - display: flex; - align-items: center; - justify-content: center; - @include Typography.EllipsisOverflow; - } - } -} - -.markdown { - p { - margin: 0; - padding-block: Spacings.$spacing06; - align-items: center; - } - - ul { - list-style-type: disc; - margin-top: 0; - padding: 0; - margin-left: Spacings.$spacing05; - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - - li { - white-space-collapse: collapse; - } - } - - ol { - list-style-type: decimal; - padding-left: Spacings.$spacing05; - list-style-position: outside; - - li { - white-space-collapse: collapse; - } - } - - h1 { - @include Typography.H1; - } - - h2 { - @include Typography.H2; - } - - h3 { - @include Typography.H3; - } - - table { - width: 100%; - border-collapse: collapse; - margin: Spacings.$spacing05 0; - } - - thead { - background-color: var(--background-1); - } - - tr { - border-bottom: 1px solid var(--border-0); - } - - th, - td { - padding: Spacings.$spacing03; - text-align: left; - } - - th { - font-weight: bold; - } - - pre[class*="language-"] { - background: var(--background-5); - color: var(--white-0); - padding: Spacings.$spacing05; - border-radius: Radius.$normal; - overflow: auto; - margin: 0 0 Spacings.$spacing05 0; - white-space: pre-wrap; - font-size: Typography.$small; - font-family: "Courier New", Courier, monospace; - } - - code[class*="language-"] { - background: none; - color: inherit; - border-radius: Radius.$normal; - font-family: "Courier New", Courier, monospace; - font-size: Typography.$small; - white-space: pre-wrap; - } - - code { - background: var(--background-5); - color: var(--white-0); - padding: Spacings.$spacing01; - border-radius: Radius.$normal; - font-family: "Courier New", Courier, monospace; - font-size: Typography.$medium; - } - - .code_block { - .icon { - position: absolute; - right: 0; - padding: Spacings.$spacing05; - } - code { - white-space: pre-wrap; - } - } -} diff --git a/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.tsx b/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.tsx deleted file mode 100644 index c7077523d..000000000 --- a/frontend/app/quality-assistant/ProcessTab/Process/ProcessLine.tsx +++ /dev/null @@ -1,173 +0,0 @@ -"use client"; - -import { capitalCase } from "change-case"; -import format from "date-fns/format"; -import { fr } from "date-fns/locale"; -import { saveAs } from "file-saver"; -import { useState } from "react"; -import ReactMarkdown from "react-markdown"; -import gfm from "remark-gfm"; - -import { useAssistants } from "@/lib/api/assistants/useAssistants"; -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import { Tag } from "@/lib/components/ui/Tag/Tag"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import styles from "./ProcessLine.module.scss"; - -import { Process } from "../../types/process"; - -interface ProcessLineProps { - process: Process; - last?: boolean; - selected: boolean; - setSelected: (selected: boolean, event: React.MouseEvent) => void; -} - -const ProcessLine = ({ - process, - last, - selected, - setSelected, -}: ProcessLineProps): JSX.Element => { - const [showResult, setShowResult] = useState(false); - const [downloadUrl, setDownloadUrl] = useState(null); - const { isMobile } = useDevice(); - const { downloadTaskResult } = useAssistants(); - - const handleMouseEnter = async () => { - if (process.status === "completed" && !downloadUrl) { - const res: string = await downloadTaskResult(process.id); - setDownloadUrl(res); - } - }; - - const handleDownload = async () => { - if (downloadUrl) { - const response = await fetch( - downloadUrl.replace("host.docker.internal", "localhost") - ); - const blob = await response.blob(); - const formattedDate = format( - new Date(process.creation_time), - "yyyy-MM-dd", - { locale: fr } - ); - const fileName = `${process.assistant_name}_${formattedDate}.pdf`; - saveAs(blob, fileName); - } - }; - - return ( - <> -
{ - if (process.status === "completed") { - setShowResult(!showResult); - } - }} - onMouseEnter={() => void handleMouseEnter()} - > -
- setSelected(checked, event)} - /> -
- {process.assistant_name} - - {process.task_metadata.input_files.map((file, index) => ( -
- {file} -
- ))} -
-
-
-
- {!isMobile && ( - <> - - {format( - new Date(process.creation_time), - "d MMMM yyyy '-' HH:mm:ss", - { - locale: fr, - } - )} - -
- -
- - )} -
) => { - event.stopPropagation(); - }} - > - {process.status === "processing" ? ( - - ) : downloadUrl ? ( -
void handleDownload()}> - -
- ) : ( - - )} -
-
-
- - } - > - {process.answer && ( -
- - {process.answer.replace(/\n/g, "\n")} - -
- )} -
- - ); -}; - -export default ProcessLine; diff --git a/frontend/app/quality-assistant/ProcessTab/ProcessTab.module.scss b/frontend/app/quality-assistant/ProcessTab/ProcessTab.module.scss deleted file mode 100644 index eda0ade1c..000000000 --- a/frontend/app/quality-assistant/ProcessTab/ProcessTab.module.scss +++ /dev/null @@ -1,122 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/Variables.module.scss"; - -.process_tab_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - padding-bottom: Spacings.$spacing10; - border-radius: Radius.$normal; - - @media screen and (max-width: ScreenSizes.$small) { - overflow-x: auto; - } - - .title { - @include Typography.H2; - } - - .table_header { - display: flex; - justify-content: space-between; - align-items: center; - gap: Spacings.$spacing03; - - .search { - width: 250px; - } - } - - .first_line { - display: flex; - justify-content: space-between; - padding-left: calc(Spacings.$spacing06); - padding-right: calc(Spacings.$spacing11 + 6px); - padding-block: Spacings.$spacing02; - font-weight: 500; - background-color: var(--background-1); - font-size: Typography.$small; - border: 1px solid var(--border-0); - border-radius: Radius.$normal Radius.$normal 0 0; - border-bottom: none; - - &.empty { - border: 1px solid var(--border-0); - border-radius: Radius.$normal; - } - - .left { - display: flex; - align-items: center; - gap: calc(Spacings.$spacing06 + 6px); - - .left_fields { - display: flex; - align-items: center; - - .field { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - cursor: pointer; - - .icon { - visibility: hidden; - } - - &:hover { - .icon { - visibility: visible; - } - } - - &.assistant { - width: Variables.$menuSectionWidth; - } - } - } - } - - .right { - display: flex; - gap: calc(Spacings.$spacing12 + Spacings.$spacing06 + 2px); - - .status { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - cursor: pointer; - - .icon { - visibility: hidden; - } - - &:hover { - .icon { - visibility: visible; - } - } - } - - .date { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - cursor: pointer; - - .icon { - visibility: hidden; - } - - &:hover { - .icon { - visibility: visible; - } - } - } - } - } -} diff --git a/frontend/app/quality-assistant/ProcessTab/ProcessTab.tsx b/frontend/app/quality-assistant/ProcessTab/ProcessTab.tsx deleted file mode 100644 index 18da6a403..000000000 --- a/frontend/app/quality-assistant/ProcessTab/ProcessTab.tsx +++ /dev/null @@ -1,238 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -import { useAssistants } from "@/lib/api/assistants/useAssistants"; -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { filterAndSort, updateSelectedItems } from "@/lib/helpers/table"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import ProcessLine from "./Process/ProcessLine"; -import styles from "./ProcessTab.module.scss"; - -import { Process } from "../types/process"; - -const ProcessTab = (): JSX.Element => { - const [processes, setProcesses] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [selectedProcess, setSelectedProcess] = useState([]); - const [allChecked, setAllChecked] = useState(false); - const [sortConfig, setSortConfig] = useState<{ - key: keyof Process; - direction: "ascending" | "descending"; - }>({ key: "creation_time", direction: "descending" }); - const [filteredProcess, setFilteredProcess] = useState([]); - const [lastSelectedIndex, setLastSelectedIndex] = useState( - null - ); - const [loading, setLoading] = useState(false); - - const { getTasks, deleteTask } = useAssistants(); - const { supabase } = useSupabase(); - const { isMobile } = useDevice(); - - const loadTasks = async () => { - try { - const res = await getTasks(); - setProcesses(res); - setFilteredProcess(res); - } catch (error) { - console.error(error); - } - }; - - const handleStatusChange = () => { - void loadTasks(); - }; - - useEffect(() => { - void loadTasks(); - }, []); - - useEffect(() => { - const channel = supabase - .channel("tasks") - .on( - "postgres_changes", - { event: "UPDATE", schema: "public", table: "tasks" }, - handleStatusChange - ) - .subscribe(); - - return () => { - void supabase.removeChannel(channel); - }; - }, []); - - useEffect(() => { - setFilteredProcess( - filterAndSort( - processes, - searchQuery, - sortConfig, - (process) => process[sortConfig.key] - ) - ); - }, [processes, searchQuery, sortConfig]); - - const handleDelete = async () => { - setLoading(true); - await Promise.all( - selectedProcess.map(async (process) => await deleteTask(process.id)) - ); - - const remainingProcesses = processes.filter( - (process) => - !selectedProcess.some((selected) => selected.id === process.id) - ); - - setProcesses(remainingProcesses); - setFilteredProcess( - filterAndSort( - remainingProcesses, - searchQuery, - sortConfig, - (process) => process[sortConfig.key] - ) - ); - - setSelectedProcess([]); - setAllChecked(false); - setLoading(false); - }; - - const handleSelect = ( - process: Process, - index: number, - event: React.MouseEvent - ) => { - const newSelectedProcess = updateSelectedItems({ - item: process, - index, - event, - lastSelectedIndex, - filteredList: filteredProcess, - selectedItems: selectedProcess, - }); - setSelectedProcess(newSelectedProcess.selectedItems); - setLastSelectedIndex(newSelectedProcess.lastSelectedIndex); - }; - - const handleSort = (key: keyof Process) => { - setSortConfig((prevSortConfig) => { - let direction: "ascending" | "descending" = "ascending"; - if ( - prevSortConfig.key === key && - prevSortConfig.direction === "ascending" - ) { - direction = "descending"; - } - - return { key, direction }; - }); - }; - - return ( -
- My Results -
-
- -
- -
-
-
-
- { - setAllChecked(checked); - setSelectedProcess(checked ? filteredProcess : []); - }} - /> -
-
handleSort("assistant_name")} - > - Assistant -
- -
-
-
handleSort("name")}> - Files -
- -
-
-
-
-
- {!isMobile && ( - <> -
handleSort("creation_time")} - > - Date -
- -
-
-
handleSort("status")} - > - Statut -
- -
-
- - )} -
-
-
- {filteredProcess.map((process, index) => ( -
- item.id === process.id - )} - setSelected={(_selected, event) => - handleSelect(process, index, event) - } - /> -
- ))} -
-
-
- ); -}; - -export default ProcessTab; diff --git a/frontend/app/quality-assistant/page.module.scss b/frontend/app/quality-assistant/page.module.scss deleted file mode 100644 index 27b57110d..000000000 --- a/frontend/app/quality-assistant/page.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.page_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - width: 100%; - height: 100vh; - overflow: hidden; - - .content_wrapper { - padding-inline: Spacings.$spacing09; - padding-block: Spacings.$spacing05; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - height: 100%; - } -} diff --git a/frontend/app/quality-assistant/page.tsx b/frontend/app/quality-assistant/page.tsx deleted file mode 100644 index a4da3eb48..000000000 --- a/frontend/app/quality-assistant/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; - -import { useState } from "react"; - -import PageHeader from "@/lib/components/PageHeader/PageHeader"; -import { Tabs } from "@/lib/components/ui/Tabs/Tabs"; -import { Tab } from "@/lib/types/Tab"; - -import AssistantTab from "./AssistantTab/AssistantTab"; -import ProcessTab from "./ProcessTab/ProcessTab"; -import styles from "./page.module.scss"; - -const QualityAssistant = (): JSX.Element => { - const [selectedTab, setSelectedTab] = useState("Assistants"); - - const qualityAssistantTab: Tab[] = [ - { - label: "Assistants", - isSelected: selectedTab === "Assistants", - onClick: () => setSelectedTab("Assistants"), - iconName: "assistant", - }, - { - label: "Process", - isSelected: selectedTab === "Process", - onClick: () => setSelectedTab("Process"), - iconName: "waiting", - }, - ]; - - return ( -
-
- -
-
- - {selectedTab === "Assistants" && ( - - )} - {selectedTab === "Process" && } -
-
- ); -}; - -export default QualityAssistant; diff --git a/frontend/app/quality-assistant/types/assistant.ts b/frontend/app/quality-assistant/types/assistant.ts deleted file mode 100644 index 3da1bca15..000000000 --- a/frontend/app/quality-assistant/types/assistant.ts +++ /dev/null @@ -1,123 +0,0 @@ -interface Pricing { - cost: number; - description: string; -} - -interface InputFile { - key: string; - allowed_extensions: string[]; - required: boolean; - description: string; -} - -interface InputUrl { - key: string; - required: boolean; - description: string; -} - -interface InputText { - key: string; - required: boolean; - description: string; - validation_regex: string; -} - -interface InputBoolean { - key: string; - required: boolean; - description: string; -} - -interface InputNumber { - key: string; - required: boolean; - description: string; - min: number; - max: number; - increment: number; - default: number; -} - -interface SelectText { - key: string; - required: boolean; - description: string; - options: string[]; - default: string; -} - -interface SelectNumber { - key: string; - required: boolean; - description: string; - options: number[]; - default: number; -} - -interface Brain { - required: boolean; - description: string; - type: string; -} - -interface Inputs { - files: InputFile[]; - urls: InputUrl[]; - texts: InputText[]; - booleans?: InputBoolean[]; - numbers: InputNumber[]; - select_texts?: SelectText[]; - select_numbers: SelectNumber[]; - brain: Brain; - conditional_inputs?: ConditionalInput[]; -} - -export interface Assistant { - id: number; - name: string; - description: string; - pricing: Pricing; - tags: string[]; - input_description: string; - output_description: string; - inputs: Inputs; - icon_url: string; -} - -interface ProcessAssistantInputFile { - key: string; - value: string; -} - -export interface ConditionalInput { - key: string; - conditional_key: string; - condition: "equals" | "not_equals"; - value: string; -} - -export interface ProcessAssistantData { - id: number; - name: string; - inputs: { - files?: ProcessAssistantInputFile[]; - urls?: { key: string; value: string }[]; - texts?: { key: string; value: string }[]; - booleans?: { key: string; value: boolean | null }[]; - numbers?: { key: string; value: number }[]; - select_texts?: { key: string; value: string | null }[]; - select_numbers?: { key: string; value: number }[]; - brain?: { value: string }; - conditional_inputs?: ConditionalInput[]; - }; -} - -export interface ProcessAssistantInput { - input: ProcessAssistantData; - files: File[]; -} - -export interface ResultDownload { - data: string; -} diff --git a/frontend/app/quality-assistant/types/process.ts b/frontend/app/quality-assistant/types/process.ts deleted file mode 100644 index 712bd6df3..000000000 --- a/frontend/app/quality-assistant/types/process.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface ProcessMetadata { - input_files: string[]; -} - -export interface Process { - answer: string; - id: number; - name: string; - creation_time: string; - status: "pending" | "processing" | "completed" | "error"; - assistant_name: string; - task_metadata: ProcessMetadata; -} diff --git a/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss b/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss deleted file mode 100644 index 151b7aa38..000000000 --- a/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss +++ /dev/null @@ -1,77 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/Variables.module.scss"; - -.brain_button_container { - display: flex; - padding: Spacings.$spacing04; - border: 1px solid var(--border-0); - border-radius: Radius.$normal; - cursor: pointer; - gap: Spacings.$spacing03; - flex-direction: column; - align-items: center; - height: Variables.$brainButtonHeight; - overflow: hidden; - - &:hover { - border-color: var(--primary-0); - } - - &.not_hovered { - &:hover { - border-color: var(--border-0); - } - } - - .header { - display: flex; - gap: Spacings.$spacing05; - width: 100%; - align-items: center; - overflow: hidden; - justify-content: space-between; - - .left { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - overflow: hidden; - - .brain_image { - border-radius: Radius.$normal; - } - - .name { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - font-weight: 500; - } - - .brain_snippet { - min-width: 24px; - min-height: 24px; - max-width: 24px; - max-height: 24px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$tiny; - } - } - } - - .description { - font-size: Typography.$tiny; - color: var(--text-4); - width: 100%; - font-style: italic; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - text-overflow: ellipsis; - } -} diff --git a/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx b/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx deleted file mode 100644 index 6e4bccb91..000000000 --- a/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import { UUID } from "crypto"; -import Image from "next/image"; -import { useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { BrainType } from "@/lib/types/BrainConfig"; - -import styles from "./BrainButton.module.scss"; - -export interface BrainOrModel { - name: string; - description: string; - id: UUID; - brain_type: BrainType; - image_url?: string; - price?: number; - display_name?: string; - snippet_emoji?: string; - snippet_color?: string; -} -interface BrainButtonProps { - brainOrModel: BrainOrModel; - newBrain: () => void; -} - -const BrainButton = ({ - brainOrModel, - newBrain, -}: BrainButtonProps): JSX.Element => { - const [optionsHovered, setOptionsHovered] = useState(false); - const { setCurrentBrainId } = useBrainContext(); - - return ( -
{ - setCurrentBrainId(brainOrModel.id); - newBrain(); - }} - > -
-
- {brainOrModel.image_url ? ( - Brain or Model - ) : ( -
- {brainOrModel.snippet_emoji} -
- )} - - {brainOrModel.display_name ?? brainOrModel.name} - -
- {brainOrModel.brain_type === "doc" && ( -
{ - event.stopPropagation(); - window.location.href = `/studio/${brainOrModel.id}`; - }} - onMouseEnter={() => setOptionsHovered(true)} - onMouseLeave={() => setOptionsHovered(false)} - > - -
- )} -
- {brainOrModel.description} -
- ); -}; - -export default BrainButton; diff --git a/frontend/app/search/BrainsList/BrainsList.module.scss b/frontend/app/search/BrainsList/BrainsList.module.scss deleted file mode 100644 index 82d93effd..000000000 --- a/frontend/app/search/BrainsList/BrainsList.module.scss +++ /dev/null @@ -1,39 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Variables.module.scss"; - -.brains_list_container { - display: flex; - margin-inline: -(Spacings.$spacing10); - gap: calc(Spacings.$spacing05 + Spacings.$spacing01); - - .brains_list_wrapper { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: Spacings.$spacing03; - overflow: hidden; - width: 100%; - overflow-y: auto; - - @media screen and (min-width: ScreenSizes.$large) { - grid-template-columns: repeat(3, minmax(200px, 1fr)); - } - } - - .chevron { - visibility: visible; - height: min-content; - padding: Spacings.$spacing03; - margin-top: calc(Variables.$brainButtonHeight / 2 - 20px); - - &:hover { - background-color: var(--background-3); - border-radius: Radius.$circle; - } - - &.disabled { - visibility: hidden; - } - } -} diff --git a/frontend/app/search/BrainsList/BrainsList.tsx b/frontend/app/search/BrainsList/BrainsList.tsx deleted file mode 100644 index ba3170ab3..000000000 --- a/frontend/app/search/BrainsList/BrainsList.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import BrainButton, { BrainOrModel } from "./BrainButton/BrainButton"; -import styles from "./BrainsList.module.scss"; - -interface BrainsListProps { - brains: BrainOrModel[]; - selectedTab: string; - brainsPerPage: number; - newBrain: () => void; -} - -const BrainsList = ({ - brains, - selectedTab, - brainsPerPage, - newBrain, -}: BrainsListProps): JSX.Element => { - const [currentPage, setCurrentPage] = useState(0); - const [transitionDirection, setTransitionDirection] = useState(""); - const [filteredBrains, setFilteredBrains] = useState([]); - const [totalPages, setTotalPages] = useState(0); - - const handleNextPage = () => { - if (currentPage < totalPages - 1) { - setTransitionDirection("next"); - setCurrentPage((prevPage) => prevPage + 1); - } - }; - - const handlePreviousPage = () => { - if (currentPage > 0) { - setTransitionDirection("prev"); - setCurrentPage((prevPage) => prevPage - 1); - } - }; - - useEffect(() => { - setCurrentPage(0); - let filtered = brains; - - if (selectedTab === "Brains") { - filtered = brains.filter((brain) => brain.brain_type === "doc"); - } else if (selectedTab === "Models") { - filtered = brains.filter((brain) => brain.brain_type === "model"); - } - - setFilteredBrains(filtered); - setTotalPages(Math.ceil(filtered.length / brainsPerPage)); - }, [brains, selectedTab, brainsPerPage]); - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - switch (event.key) { - case "ArrowRight": - handleNextPage(); - break; - case "ArrowLeft": - handlePreviousPage(); - break; - default: - break; - } - }; - - document.addEventListener("keydown", handleKeyDown); - - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, [handleNextPage, handlePreviousPage]); - - const displayedItems = filteredBrains.slice( - currentPage * brainsPerPage, - (currentPage + 1) * brainsPerPage - ); - - return ( -
-
- -
-
- {displayedItems.map((item, index) => ( - - ))} -
-
= totalPages - 1 ? styles.disabled : "" - }`} - onClick={handleNextPage} - > - -
-
- ); -}; - -export default BrainsList; diff --git a/frontend/app/search/page.module.scss b/frontend/app/search/page.module.scss deleted file mode 100644 index 70447b2a9..000000000 --- a/frontend/app/search/page.module.scss +++ /dev/null @@ -1,102 +0,0 @@ -@use "styles/IconSizes.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/Variables.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.main_container { - position: relative; - width: 100%; - height: 100%; - - .page_header { - position: absolute; - width: 100%; - } - - .search_page_container { - background-color: var(--background-0); - width: 100%; - height: 100%; - display: flex; - align-items: center; - padding-top: Spacings.$spacing07; - flex-direction: column; - - .main_wrapper { - display: flex; - flex-direction: column; - row-gap: Spacings.$spacing05; - width: 50%; - margin-inline: auto; - transform: translateY(-#{Variables.$searchBarHeight}); - margin-top: 25vh; - - @media (max-width: ScreenSizes.$small) { - width: 100%; - padding-inline: Spacings.$spacing10; - } - - .quivr_logo_wrapper { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - .quivr_text { - @include Typography.Big; - - .quivr_text_primary { - color: var(--primary-0); - } - } - } - - .assistants_container { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - - .tabs { - width: 100%; - } - } - } - } -} - -.onboarding_overlay { - width: 100%; - height: 100%; - z-index: ZIndexes.$overlay; - background-color: var(--background-blur); - position: absolute; - top: 0; - left: 0; - - .main_message_wrapper { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - - .main_message { - display: flex; - flex-direction: column; - - .bolder { - font-weight: bold; - } - } - } - - .first_brain_button { - position: absolute; - right: Spacings.$spacing07; - top: Spacings.$spacing04; - display: flex; - gap: Spacings.$spacing05; - } -} diff --git a/frontend/app/search/page.tsx b/frontend/app/search/page.tsx deleted file mode 100644 index cf0334b17..000000000 --- a/frontend/app/search/page.tsx +++ /dev/null @@ -1,143 +0,0 @@ -"use client"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; - -import { QuivrLogo } from "@/lib/assets/QuivrLogo"; -import { AddBrainModal } from "@/lib/components/AddBrainModal"; -import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider"; -import { OnboardingModal } from "@/lib/components/OnboardingModal/OnboardingModal"; -import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; -import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; -import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar"; -import { SmallTabs } from "@/lib/components/ui/SmallTabs/SmallTabs"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useUserData } from "@/lib/hooks/useUserData"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; -import { ButtonType } from "@/lib/types/QuivrButton"; -import { Tab } from "@/lib/types/Tab"; - -import BrainsList from "./BrainsList/BrainsList"; -import styles from "./page.module.scss"; - -const projectName = process.env.NEXT_PUBLIC_PROJECT_NAME; - -const Search = (): JSX.Element => { - const [selectedTab, setSelectedTab] = useState("Models"); - const [isNewBrain, setIsNewBrain] = useState(false); - const brainsPerPage = 6; - - const pathname = usePathname(); - const { session } = useSupabase(); - const { setIsBrainCreationModalOpened } = useBrainCreationContext(); - const { userData } = useUserData(); - const { isDarkMode } = useUserSettingsContext(); - const { allBrains } = useBrainContext(); - - const [buttons, setButtons] = useState([ - { - label: "Create brain", - color: "primary", - onClick: () => { - setIsBrainCreationModalOpened(true); - }, - iconName: "brain", - tooltip: - "You have reached the maximum number of brains allowed. Please upgrade your plan or delete some brains to create a new one.", - }, - ]); - - const assistantsTabs: Tab[] = [ - { - label: "Models", - isSelected: selectedTab === "Models", - onClick: () => setSelectedTab("Models"), - iconName: "file", - }, - { - label: "Brains", - isSelected: selectedTab === "Brains", - onClick: () => setSelectedTab("Brains"), - iconName: "settings", - }, - { - label: "All", - isSelected: selectedTab === "All", - onClick: () => setSelectedTab("All"), - iconName: "settings", - }, - ]; - - const newBrain = () => { - setIsNewBrain(true); - setTimeout(() => { - setIsNewBrain(false); - }, 750); - }; - - useEffect(() => { - if (userData) { - setButtons((prevButtons) => { - return prevButtons.map((button) => { - if (button.label === "Create brain") { - return { - ...button, - disabled: - userData.max_brains <= - allBrains.filter((brain) => brain.brain_type === "doc").length, - }; - } - - return button; - }); - }); - } - }, [userData?.max_brains, allBrains.length]); - - useEffect(() => { - if (session === null) { - redirectToLogin(); - } - }, [pathname, session]); - - return ( -
-
- -
-
-
-
- -
- Talk to - - {projectName ? projectName : "Quivr"} - -
-
-
- -
-
-
- -
- -
-
-
- - - -
- ); -}; - -export default Search; diff --git a/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.module.scss b/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.module.scss deleted file mode 100644 index afb3fbe3f..000000000 --- a/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.analytics_wrapper { - padding-block: Spacings.$spacing06; - display: flex; - flex-direction: column; - - .selectors_wrapper { - display: flex; - justify-content: space-between; - - .selector { - width: 300px; - } - } -} diff --git a/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.tsx b/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.tsx deleted file mode 100644 index f25546b5c..000000000 --- a/frontend/app/studio/BrainsTabs/components/Analytics/Analytics.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { - CategoryScale, - ChartDataset, - Chart as ChartJS, - Filler, - Legend, - LinearScale, - LineElement, - PointElement, - ScriptableContext, - Title, - Tooltip, -} from "chart.js"; -import { useLayoutEffect, useState } from "react"; -import { Line } from "react-chartjs-2"; - -import { formatMinimalBrainsToSelectComponentInput } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/utils/formatMinimalBrainsToSelectComponentInput"; -import { Range } from "@/lib/api/analytics/types"; -import { useAnalytics } from "@/lib/api/analytics/useAnalyticsApi"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; -import { SingleSelector } from "@/lib/components/ui/SingleSelector/SingleSelector"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import styles from "./Analytics.module.scss"; - -ChartJS.register( - CategoryScale, - Filler, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend -); - -export const Analytics = (): JSX.Element => { - const { isMobile } = useDevice(); - const { getBrainsUsages } = useAnalytics(); - const { allBrains } = useBrainContext(); - const [chartData, setChartData] = useState({ - labels: [] as Date[], - datasets: [{}] as ChartDataset<"line", number[]>[], - }); - const [currentChartRange, setCurrentChartRange] = useState( - Range.WEEK as number - ); - const [selectedBrainId, setSelectedBrainId] = useState(null); - - const graphRangeOptions = [ - { label: "Last 7 days", value: Range.WEEK }, - { label: "Last 30 days", value: Range.MONTH }, - { label: "Last 90 days", value: Range.QUARTER }, - ]; - - const brainsWithUploadRights = formatMinimalBrainsToSelectComponentInput( - allBrains.filter((brain) => brain.brain_type === "doc") - ); - - const selectedGraphRangeOption = graphRangeOptions.find( - (option) => option.value === currentChartRange - ); - - const handleGraphRangeChange = (newValue: number) => { - setCurrentChartRange(newValue); - }; - - useLayoutEffect(() => { - void (async () => { - try { - const res = await getBrainsUsages(selectedBrainId, currentChartRange); - const chartLabels = res?.usages.map((usage) => usage.date) as Date[]; - const chartDataset = res?.usages.map( - (usage) => usage.usage_count - ) as number[]; - - setChartData({ - labels: chartLabels, - datasets: [ - { - label: `Daily questions to ${ - selectedBrainId - ? allBrains.find((brain) => brain.id === selectedBrainId) - ?.name - : "your brains" - }`, - data: chartDataset, - borderColor: "rgb(75, 192, 192)", - backgroundColor: (context: ScriptableContext<"line">) => { - const ctx = context.chart.ctx; - const gradient = ctx.createLinearGradient(100, 100, 100, 250); - gradient.addColorStop(0, "rgba(75, 192, 192, 0.4)"); - gradient.addColorStop(1, "rgba(75, 192, 192, 0.05)"); - - return gradient; - }, - fill: true, - tension: 0.2, - }, - ], - }); - } catch (error) { - console.error(error); - } - })(); - }, [chartData.labels.length, currentChartRange, selectedBrainId]); - - const options = { - type: "line", - scales: { - x: { - grid: { - display: false, - }, - }, - y: { - beginAtZero: true, - grid: { - display: false, - }, - ticks: { - stepSize: 1, - }, - }, - }, - }; - - return ( -
- {!isMobile ? ( -
- {chartData.labels.length ? ( - <> -
-
- handleGraphRangeChange(option)} - selectedOption={selectedGraphRangeOption} - placeholder="Select range" - /> -
-
- setSelectedBrainId(brainId)} - onBackClick={() => setSelectedBrainId(null)} - selectedOption={ - selectedBrainId - ? { - value: selectedBrainId, - label: allBrains.find( - (brain) => brain.id === selectedBrainId - )?.name as string, - } - : undefined - } - placeholder="Select specific brain" - /> -
-
- - - ) : ( - - )} -
- ) : ( - - This feature is not available on small screens - - )} -
- ); -}; diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainSearchBar.tsx b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainSearchBar.tsx deleted file mode 100644 index a73589133..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainSearchBar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; - -type BrainSearchBarProps = { - searchQuery: string; - setSearchQuery: (searchQuery: string) => void; -}; - -export const BrainSearchBar = ({ - searchQuery, - setSearchQuery, -}: BrainSearchBarProps): JSX.Element => { - const { t } = useTranslation(["brain"]); - - return ( - - ); -}; diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss deleted file mode 100644 index 4638607ae..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss +++ /dev/null @@ -1,102 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.brain_item_wrapper { - padding-inline: Spacings.$spacing05; - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - align-items: center; - cursor: pointer; - border: 1px solid var(--border-1); - border-radius: Radius.$normal; - padding-block: Spacings.$spacing04; - position: relative; - overflow: visible; - height: 100%; - box-shadow: BoxShadow.$small; - - &:hover { - border-color: var(--primary-0); - color: var(--text-3); - } - - &.not_hovered { - &:hover { - border-color: var(--border-1); - } - } - - .brain_header { - display: flex; - justify-content: space-between; - width: 100%; - align-items: center; - overflow: hidden; - gap: Spacings.$spacing03; - - .left { - display: flex; - gap: Spacings.$spacing03; - @include Typography.H2; - align-items: center; - overflow: hidden; - - .brain_snippet { - min-width: 24px; - min-height: 24px; - max-width: 24px; - max-height: 24px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$tiny; - } - - .name { - @include Typography.EllipsisOverflow; - } - } - } - - .model { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - font-size: Typography.$small; - width: 100%; - - .title { - font-weight: 500; - } - - .model_type { - color: var(--text-4); - } - } - - .description { - width: 100%; - font-style: italic; - font-size: Typography.$tiny; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 5; - overflow: hidden; - text-overflow: ellipsis; - color: var(--text-4); - } - - .options_modal { - position: absolute; - right: Spacings.$spacing02; - top: Spacings.$spacing08; - z-index: ZIndexes.$modal; - padding-bottom: Spacings.$spacing01; - } -} diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx deleted file mode 100644 index 984cf7878..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -import { DeleteOrUnsubscribeConfirmationModal } from "@/app/studio/[brainId]/BrainManagementTabs/components/DeleteOrUnsubscribeModal/DeleteOrUnsubscribeConfirmationModal"; -import { useBrainFetcher } from "@/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher"; -import { useBrainManagementTabs } from "@/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainManagementTabs"; -import { getBrainPermissions } from "@/app/studio/[brainId]/BrainManagementTabs/utils/getBrainPermissions"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { OptionsModal } from "@/lib/components/ui/OptionsModal/OptionsModal"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; -import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { Option } from "@/lib/types/Options"; - -import styles from "./BrainItem.module.scss"; - -type BrainItemProps = { - brain: MinimalBrainForUser; - even: boolean; -}; - -export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { - const [optionsOpened, setOptionsOpened] = useState(false); - const [optionsHovered, setOptionsHovered] = useState(false); - - const { - handleUnsubscribeOrDeleteBrain, - isDeleteOrUnsubscribeModalOpened, - setIsDeleteOrUnsubscribeModalOpened, - isDeleteOrUnsubscribeRequestPending, - } = useBrainManagementTabs(brain.id); - const { allBrains, setCurrentBrainId } = useBrainContext(); - const { isOwnedByCurrentUser } = getBrainPermissions({ - brainId: brain.id, - userAccessibleBrains: allBrains, - }); - const { brain: fetchedBrain } = useBrainFetcher({ brainId: brain.id }); - const { isDarkMode } = useUserSettingsContext(); - const { setIsVisible } = useSearchModalContext(); - - const iconRef = useRef(null); - const optionsRef = useRef(null); - - const options: Option[] = [ - { - label: "Edit", - onClick: () => (window.location.href = `/studio/${brain.id}`), - iconName: "edit", - iconColor: "primary", - }, - { - label: "Talk to Brain", - onClick: () => { - setOptionsOpened(false); - setIsVisible(true); - setTimeout(() => setCurrentBrainId(brain.id)); - }, - iconName: "chat", - iconColor: "primary", - }, - { - label: "Delete", - onClick: () => void setIsDeleteOrUnsubscribeModalOpened(true), - iconName: "delete", - iconColor: "dangerous", - }, - ]; - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - iconRef.current && - !iconRef.current.contains(event.target as Node) && - optionsRef.current && - !optionsRef.current.contains(event.target as Node) - ) { - setOptionsOpened(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [isDarkMode]); - - return ( - <> - -
-
-
- {brain.snippet_emoji} -
- {brain.name} -
-
) => { - event.nativeEvent.stopImmediatePropagation(); - event.stopPropagation(); - event.preventDefault(); - setOptionsOpened(!optionsOpened); - }} - className={styles.icon} - > -
setOptionsHovered(true)} - onMouseLeave={() => setOptionsHovered(false)} - > - -
-
-
-
- Model: - - {fetchedBrain?.model ?? "gpt-3.5-turbo-0125"} - -
- - {brain.description} -
- void handleUnsubscribeOrDeleteBrain()} - isOwnedByCurrentUser={isOwnedByCurrentUser} - isDeleteOrUnsubscribeRequestPending={ - isDeleteOrUnsubscribeRequestPending - } - /> -
-
setOptionsHovered(true)} - onMouseLeave={() => setOptionsHovered(false)} - > - {optionsOpened && } -
-
- - ); -}; diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.module.scss b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.module.scss deleted file mode 100644 index 097d505a5..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brains_wrapper { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - row-gap: Spacings.$spacing04; - column-gap: Spacings.$spacing04; - border-radius: Radius.$big; - overflow: hidden; - width: 100%; - overflow-y: auto; - padding: Spacings.$spacing01; - padding-bottom: Spacings.$spacing07; -} diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.tsx b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.tsx deleted file mode 100644 index 3de501556..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainsList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -import { BrainItem } from "./BrainItem/BrainItem"; -import styles from "./BrainsList.module.scss"; - -type BrainsListProps = { - brains: MinimalBrainForUser[]; -}; - -export const BrainsList = ({ brains }: BrainsListProps): JSX.Element => { - return ( -
- {brains.map((brain, index) => ( -
- -
- ))} -
- ); -}; diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.module.scss b/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.module.scss deleted file mode 100644 index 222b29214..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.manage_brains_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - padding-block: Spacings.$spacing05; - height: 100%; - - .search_brain { - width: 250px; - } -} diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.tsx b/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.tsx deleted file mode 100644 index 588e7add8..000000000 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/ManageBrains.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import Spinner from "@/lib/components/ui/Spinner"; - -import { BrainSearchBar } from "./BrainSearchBar"; -import { BrainsList } from "./BrainsList/BrainsList"; -import styles from "./ManageBrains.module.scss"; - -import { useBrainsTabs } from "../../hooks/useBrainsTabs"; - -export const ManageBrains = (): JSX.Element => { - const { searchQuery, isFetchingBrains, setSearchQuery, brains } = - useBrainsTabs(); - - if (isFetchingBrains && brains.length === 0) { - return ( -
- -
- ); - } - - return ( -
-
- -
- - brain.brain_type === "doc")} - /> -
- ); -}; diff --git a/frontend/app/studio/BrainsTabs/hooks/useBrainsTabs.ts b/frontend/app/studio/BrainsTabs/hooks/useBrainsTabs.ts deleted file mode 100644 index fbdb4d013..000000000 --- a/frontend/app/studio/BrainsTabs/hooks/useBrainsTabs.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useState } from "react"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainsTabs = () => { - const [searchQuery, setSearchQuery] = useState(""); - const { allBrains, isFetchingBrains } = useBrainContext(); - - const brains = allBrains.filter((brain) => { - const query = searchQuery.toLowerCase(); - const name = brain.name.toLowerCase(); - - return name.includes(query); - }); - - const privateBrains = brains.filter((brain) => brain.status === "private"); - - const publicBrains = brains.filter((brain) => brain.status === "public"); - - return { - searchQuery, - setSearchQuery, - brains, - privateBrains, - publicBrains, - isFetchingBrains, - }; -}; diff --git a/frontend/app/studio/BrainsTabs/types.ts b/frontend/app/studio/BrainsTabs/types.ts deleted file mode 100644 index 2aef46648..000000000 --- a/frontend/app/studio/BrainsTabs/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -const brainTabs = ["all", "private", "public"] as const; -export type BrainTab = (typeof brainTabs)[number]; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.module.scss deleted file mode 100644 index 5d884f035..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.loader { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; -} - -.header_wrapper { - display: flex; - width: calc(100% + (Spacings.$spacing05 + Spacings.$spacing03)); - margin-left: -(Spacings.$spacing05 + Spacings.$spacing03); - gap: Spacings.$spacing03; - align-items: center; - padding-top: Spacings.$spacing05; - - .tabs { - width: 100%; - } -} diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.tsx deleted file mode 100644 index 69db87470..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/BrainManagementTabs.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable max-lines */ - -import { useRouter } from "next/navigation"; -import { useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { Tabs } from "@/lib/components/ui/Tabs/Tabs"; -import { Tab } from "@/lib/types/Tab"; - -import styles from "./BrainManagementTabs.module.scss"; -import { KnowledgeTab } from "./components/KnowledgeTab/KnowledgeTab"; -import { useAddedKnowledge } from "./components/KnowledgeTab/hooks/useAddedKnowledge"; -import { PeopleTab } from "./components/PeopleTab/PeopleTab"; -import { SettingsTab } from "./components/SettingsTab/SettingsTab"; -import { useBrainFetcher } from "./hooks/useBrainFetcher"; -import { useBrainManagementTabs } from "./hooks/useBrainManagementTabs"; - -export const BrainManagementTabs = (): JSX.Element => { - const [selectedTab, setSelectedTab] = useState("Knowledge"); - const { brainId, hasEditRights } = useBrainManagementTabs(); - const { allKnowledge } = useAddedKnowledge({ brainId: brainId ?? undefined }); - const router = useRouter(); - - const { isLoading } = useBrainFetcher({ - brainId, - }); - - const brainManagementTabs: Tab[] = [ - { - label: hasEditRights - ? `Knowledge${allKnowledge.length > 1 ? "s" : ""} (${ - allKnowledge.length - })` - : "Knowledge", - isSelected: selectedTab === "Knowledge", - onClick: () => setSelectedTab("Knowledge"), - iconName: "file", - }, - { - label: "Settings", - isSelected: selectedTab === "Settings", - onClick: () => setSelectedTab("Settings"), - iconName: "settings", - }, - { - label: "People", - isSelected: selectedTab === "People", - onClick: () => setSelectedTab("People"), - iconName: "user", - disabled: !hasEditRights, - }, - ]; - - if (!brainId) { - return
; - } - - if (isLoading) { - return ( -
- -
- ); - } - - return ( -
-
- router.push("/studio")} - /> -
- -
-
- {selectedTab === "Settings" && ( - - )} - {selectedTab === "People" && } - {selectedTab === "Knowledge" && ( - - )} -
- ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/DeleteOrUnsubscribeModal/DeleteOrUnsubscribeConfirmationModal.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/DeleteOrUnsubscribeModal/DeleteOrUnsubscribeConfirmationModal.tsx deleted file mode 100644 index 09567b921..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/DeleteOrUnsubscribeModal/DeleteOrUnsubscribeConfirmationModal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; - -type DeleteOrUnsubscribeConfirmationModalProps = { - isOpen: boolean; - setOpen: (isOpen: boolean) => void; - onConfirm: () => void; - isOwnedByCurrentUser: boolean; - isDeleteOrUnsubscribeRequestPending: boolean; -}; - -export const DeleteOrUnsubscribeConfirmationModal = ({ - isOpen, - setOpen, - onConfirm, - isOwnedByCurrentUser, - isDeleteOrUnsubscribeRequestPending, -}: DeleteOrUnsubscribeConfirmationModalProps): JSX.Element => { - const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]); - - return ( - } - CloseTrigger={
} - > -
- setOpen(false)} - label={t("returnButton")} - iconName="chevronLeft" - color="primary" - > - -
- - ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss deleted file mode 100644 index 3d1f588a6..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/BoxShadow.module.scss"; -@use "styles/Spacings.module.scss"; - -.knowledge_tab_container { - padding-block: Spacings.$spacing05; - - .knowledge_tab_wrapper { - display: flex; - flex-direction: column; - width: 100%; - gap: Spacings.$spacing05; - padding-block: Spacings.$spacing05; - - .message { - display: inline-flex; - flex-wrap: wrap; - align-items: center; - gap: Spacings.$spacing03; - } - } -} diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx deleted file mode 100644 index 1cf5653ad..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; -import { UUID } from "crypto"; -import { AnimatePresence, motion } from "framer-motion"; - -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { Knowledge } from "@/lib/types/Knowledge"; - -import styles from "./KnowledgeTab.module.scss"; -import KnowledgeTable from "./KnowledgeTable/KnowledgeTable"; -import { useAddedKnowledge } from "./hooks/useAddedKnowledge"; - -type KnowledgeTabProps = { - brainId: UUID; - hasEditRights: boolean; - allKnowledge: Knowledge[]; -}; -export const KnowledgeTab = ({ - brainId, - allKnowledge, - hasEditRights, -}: KnowledgeTabProps): JSX.Element => { - const { isPending } = useAddedKnowledge({ - brainId, - }); - const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); - - if (!hasEditRights) { - return ( -
-
- - You don't have permission to access the knowledge in this - brain. - -
-
- ); - } - - if (isPending) { - return ; - } - - if (allKnowledge.length === 0) { - return ( -
-
- -
- This brain is empty! You can add knowledge by clicking on - setShouldDisplayFeedCard(true)} - /> - . -
-
-
-
- ); - } - - return ( -
-
- - - - - -
-
- ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.module.scss deleted file mode 100644 index 429a22738..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.knowledge_item_wrapper { - padding-inline: Spacings.$spacing06; - overflow: hidden; - display: flex; - gap: Spacings.$spacing02; - justify-content: space-between; - align-items: center; - border: 1px solid var(--border-0); - padding-block: Spacings.$spacing03; - position: relative; - overflow: visible; - font-size: Typography.$small; - border-bottom: none; - - &.last { - border-radius: 0 0 Radius.$normal Radius.$normal; - border-bottom: 1px solid var(--border-0); - } - - &:hover { - background-color: var(--background-1); - cursor: pointer; - } - - .left { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - overflow: hidden; - - .file_name { - @include Typography.EllipsisOverflow; - } - } - - .right { - display: flex; - gap: Spacings.$spacing09; - align-items: center; - - .status { - width: 100px; - display: flex; - align-items: center; - justify-content: center; - } - } - - .options_modal { - position: absolute; - right: Spacings.$spacing02; - top: Spacings.$spacing08; - z-index: ZIndexes.$modal; - padding-bottom: Spacings.$spacing01; - } -} diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.tsx deleted file mode 100644 index 7a23e7fcb..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/KnowledgeItem.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import axios from "axios"; -import { capitalCase } from "change-case"; -import Image from "next/image"; -import React, { useEffect, useRef, useState } from "react"; - -import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi"; -import { useSync } from "@/lib/api/sync/useSync"; -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { OptionsModal } from "@/lib/components/ui/OptionsModal/OptionsModal"; -import { Tag } from "@/lib/components/ui/Tag/Tag"; -import { iconList } from "@/lib/helpers/iconList"; -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; -import { useDevice } from "@/lib/hooks/useDevice"; -import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge"; -import { Option } from "@/lib/types/Options"; - -import { useKnowledgeItem } from "./hooks/useKnowledgeItem"; -// eslint-disable-next-line import/order -import styles from "./KnowledgeItem.module.scss"; - -const KnowledgeItem = ({ - knowledge, - selected, - setSelected, - lastChild, -}: { - knowledge: Knowledge; - selected: boolean; - setSelected: (selected: boolean, event: React.MouseEvent) => void; - lastChild?: boolean; -}): JSX.Element => { - const [optionsOpened, setOptionsOpened] = useState(false); - const iconRef = useRef(null); - const optionsRef = useRef(null); - const { onDeleteKnowledge } = useKnowledgeItem(); - const { brain } = useUrlBrain(); - const { generateSignedUrlKnowledge } = useKnowledgeApi(); - const { isMobile } = useDevice(); - const { integrationIconUrls } = useSync(); - - const getOptions = (): Option[] => [ - { - label: "Delete", - onClick: () => void onDeleteKnowledge(knowledge), - iconName: "delete", - iconColor: "dangerous", - disabled: brain?.role !== "Owner", - }, - { - label: "Download", - onClick: () => void downloadFile(), - iconName: "download", - iconColor: "primary", - disabled: brain?.role !== "Owner" || !isUploadedKnowledge(knowledge), - }, - ]; - - const downloadFile = async () => { - if (isUploadedKnowledge(knowledge)) { - const downloadUrl = await generateSignedUrlKnowledge({ - knowledgeId: knowledge.id, - }); - - try { - const response = await axios.get(downloadUrl, { - responseType: "blob", - }); - - const blobUrl = window.URL.createObjectURL(new Blob([response.data])); - - const a = document.createElement("a"); - a.href = blobUrl; - a.download = knowledge.fileName; - document.body.appendChild(a); - a.click(); - - window.URL.revokeObjectURL(blobUrl); - } catch (error) { - console.error("Error downloading the file:", error); - } - } - setOptionsOpened(false); - }; - - const handleClickOutside = (event: MouseEvent) => { - if ( - iconRef.current && - !iconRef.current.contains(event.target as Node) && - optionsRef.current && - !optionsRef.current.contains(event.target as Node) - ) { - setOptionsOpened(false); - } - }; - - useEffect(() => { - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - const renderIcon = () => { - if (isUploadedKnowledge(knowledge)) { - return knowledge.integration ? ( - integration_icon - ) : ( - - ); - } - - return ; - }; - - const renderFileNameOrUrl = () => { - if (isUploadedKnowledge(knowledge)) { - return {knowledge.fileName}; - } - - return ( - - {knowledge.url} - - ); - }; - - return ( -
-
- setSelected(checked, event)} - /> -
{renderIcon()}
- {renderFileNameOrUrl()} -
-
- {!isMobile && ( -
- -
- )} -
) => { - event.stopPropagation(); - event.preventDefault(); - setOptionsOpened(!optionsOpened); - }} - > - -
-
-
- {optionsOpened && } -
-
- ); -}; - -export default KnowledgeItem; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/hooks/useKnowledgeItem.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/hooks/useKnowledgeItem.ts deleted file mode 100644 index 0492b53cd..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeItem/hooks/useKnowledgeItem.ts +++ /dev/null @@ -1,55 +0,0 @@ -"use client"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi"; -import { useToast } from "@/lib/hooks"; -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; -import { Knowledge } from "@/lib/types/Knowledge"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -import { useKnowledge } from "../../../hooks/useKnowledge"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useKnowledgeItem = () => { - const { deleteKnowledge } = useKnowledgeApi(); - const [isDeleting, setIsDeleting] = useState(false); - const { publish } = useToast(); - const { track } = useEventTracking(); - const { brainId } = useUrlBrain(); - const { invalidateKnowledgeDataKey } = useKnowledge({ - brainId, - }); - - const { t } = useTranslation(["translation", "explore"]); - - const onDeleteKnowledge = async (knowledge: Knowledge) => { - setIsDeleting(true); - void track("DELETE_DOCUMENT"); - const knowledge_name = - "fileName" in knowledge ? knowledge.fileName : knowledge.url; - try { - if (brainId === undefined) { - throw new Error(t("noBrain", { ns: "explore" })); - } - await deleteKnowledge({ - brainId, - knowledgeId: knowledge.id, - }); - - invalidateKnowledgeDataKey(); - } catch (error) { - publish({ - variant: "warning", - text: t("errorDeleting", { fileName: knowledge_name, ns: "explore" }), - }); - console.error("Error deleting", error); - } - setIsDeleting(false); - }; - - return { - isDeleting, - onDeleteKnowledge, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.module.scss deleted file mode 100644 index 8e5f3ea06..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.knowledge_table_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - padding-bottom: Spacings.$spacing10; - border-radius: Radius.$normal; - - .title { - @include Typography.H2; - } - - .table_header { - display: flex; - justify-content: space-between; - align-items: center; - gap: Spacings.$spacing03; - - .search { - width: 250px; - } - } - - .first_line { - display: flex; - justify-content: space-between; - padding-left: calc(Spacings.$spacing06); - padding-right: Spacings.$spacing04; - padding-block: Spacings.$spacing02; - font-weight: 500; - background-color: var(--background-1); - font-size: Typography.$small; - border: 1px solid var(--border-0); - border-radius: Radius.$normal Radius.$normal 0 0; - border-bottom: none; - - &.empty { - border: 1px solid var(--border-0); - border-radius: Radius.$normal; - } - - .left { - display: flex; - align-items: center; - gap: calc(Spacings.$spacing06 + 6px); - - .name { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - cursor: pointer; - - .icon { - visibility: hidden; - } - - &:hover { - .icon { - visibility: visible; - } - } - } - } - - .right { - display: flex; - gap: calc(Spacings.$spacing06 + 12px); - - .status { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - cursor: pointer; - - .icon { - visibility: hidden; - } - - &:hover { - .icon { - visibility: visible; - } - } - } - } - } -} diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.tsx deleted file mode 100644 index 7bfd98290..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTable/KnowledgeTable.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { Checkbox } from "@/lib/components/ui/Checkbox/Checkbox"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { updateSelectedItems } from "@/lib/helpers/table"; -import { useDevice } from "@/lib/hooks/useDevice"; -import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge"; - -import { useKnowledgeItem } from "./KnowledgeItem/hooks/useKnowledgeItem"; -// eslint-disable-next-line import/order -import KnowledgeItem from "./KnowledgeItem/KnowledgeItem"; -import styles from "./KnowledgeTable.module.scss"; - -interface KnowledgeTableProps { - knowledgeList: Knowledge[]; -} - -const filterAndSortKnowledge = ( - knowledgeList: Knowledge[], - searchQuery: string, - sortConfig: { key: string; direction: string } -): Knowledge[] => { - let filteredList = knowledgeList.filter((knowledge) => - isUploadedKnowledge(knowledge) - ? knowledge.fileName.toLowerCase().includes(searchQuery.toLowerCase()) - : knowledge.url.toLowerCase().includes(searchQuery.toLowerCase()) - ); - - if (sortConfig.key) { - const compareStrings = (a: string | number, b: string | number) => { - if (a < b) { - return sortConfig.direction === "ascending" ? -1 : 1; - } - if (a > b) { - return sortConfig.direction === "ascending" ? 1 : -1; - } - - return 0; - }; - - const getComparableValue = (item: Knowledge) => { - if (sortConfig.key === "name") { - return isUploadedKnowledge(item) ? item.fileName : item.url; - } - if (sortConfig.key === "status") { - return item.status; - } - - return ""; - }; - - filteredList = filteredList.sort((a, b) => - compareStrings(getComparableValue(a), getComparableValue(b)) - ); - } - - return filteredList; -}; - -const KnowledgeTable = React.forwardRef( - ({ knowledgeList }, ref) => { - const [selectedKnowledge, setSelectedKnowledge] = useState([]); - const [lastSelectedIndex, setLastSelectedIndex] = useState( - null - ); - const { onDeleteKnowledge } = useKnowledgeItem(); - const [allChecked, setAllChecked] = useState(false); - const [searchQuery, setSearchQuery] = useState(""); - const [filteredKnowledgeList, setFilteredKnowledgeList] = - useState(knowledgeList); - const { isMobile } = useDevice(); - const [sortConfig, setSortConfig] = useState<{ - key: string; - direction: string; - }>({ key: "", direction: "" }); - - useEffect(() => { - setFilteredKnowledgeList( - filterAndSortKnowledge(knowledgeList, searchQuery, sortConfig) - ); - }, [searchQuery, knowledgeList, sortConfig]); - - const handleSelect = ( - knowledge: Knowledge, - index: number, - event: React.MouseEvent - ) => { - const newSelectedKnowledge = updateSelectedItems({ - item: knowledge, - index, - event, - lastSelectedIndex, - filteredList: filteredKnowledgeList, - selectedItems: selectedKnowledge, - }); - setSelectedKnowledge(newSelectedKnowledge.selectedItems); - setLastSelectedIndex(newSelectedKnowledge.lastSelectedIndex); - }; - - const handleDelete = () => { - const toDelete = selectedKnowledge.filter((knowledge) => - filteredKnowledgeList.some((item) => item.id === knowledge.id) - ); - toDelete.forEach((knowledge) => { - void onDeleteKnowledge(knowledge); - }); - setSelectedKnowledge([]); - }; - - const handleSort = (key: string) => { - setSortConfig((prevSortConfig) => { - let direction = "ascending"; - if ( - prevSortConfig.key === key && - prevSortConfig.direction === "ascending" - ) { - direction = "descending"; - } - - return { key, direction }; - }); - }; - - return ( -
- Uploaded Knowledge -
-
- -
- -
-
-
-
- { - setAllChecked(checked); - setSelectedKnowledge(checked ? filteredKnowledgeList : []); - }} - /> -
handleSort("name")}> - Name -
- -
-
-
-
- {!isMobile && ( -
handleSort("status")} - > - Status -
- -
-
- )} - Actions -
-
- {filteredKnowledgeList.map((knowledge, index) => ( -
handleSelect(knowledge, index, event)} - > - item.id === knowledge.id - )} - setSelected={(_selected, event) => - handleSelect(knowledge, index, event) - } - lastChild={index === filteredKnowledgeList.length - 1} - /> -
- ))} -
-
- ); - } -); - -KnowledgeTable.displayName = "KnowledgeTable"; - -export default KnowledgeTable; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useAddedKnowledge.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useAddedKnowledge.ts deleted file mode 100644 index 984a997b5..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useAddedKnowledge.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { UUID } from "crypto"; - -import { getKnowledgeDataKey } from "@/lib/api/knowledge/config"; -import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useAddedKnowledge = ({ brainId }: { brainId?: UUID }) => { - const queryClient = useQueryClient(); - - const { getAllKnowledge } = useKnowledgeApi(); - - const fetchKnowledge = () => { - if (brainId !== undefined) { - return getAllKnowledge({ brainId }); - } - }; - - const { data: allKnowledge, isLoading: isPending } = useQuery({ - queryKey: brainId !== undefined ? [getKnowledgeDataKey(brainId)] : [], - queryFn: fetchKnowledge, - enabled: brainId !== undefined, - }); - - if (brainId === undefined) { - return { - invalidateKnowledgeDataKey: () => void {}, - isPending: false, - allKnowledge: [], - }; - } - - const knowledge_data_key = getKnowledgeDataKey(brainId); - - const invalidateKnowledgeDataKey = () => { - void queryClient.invalidateQueries({ queryKey: [knowledge_data_key] }); - }; - - return { - invalidateKnowledgeDataKey, - isPending, - allKnowledge: allKnowledge ?? [], - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useKnowledge.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useKnowledge.ts deleted file mode 100644 index a13a0ffed..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useKnowledge.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { UUID } from "crypto"; - -import { getKnowledgeDataKey } from "@/lib/api/knowledge/config"; -import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useKnowledge = ({ brainId }: { brainId?: UUID }) => { - const queryClient = useQueryClient(); - - const { getAllKnowledge } = useKnowledgeApi(); - const { data: allKnowledge, isLoading: isPending } = useQuery({ - queryKey: brainId ? [getKnowledgeDataKey(brainId)] : [], - queryFn: () => (brainId ? getAllKnowledge({ brainId: brainId }) : []), - enabled: brainId !== undefined, - }); - - if (brainId === undefined) { - return { - invalidateKnowledgeDataKey: () => void {}, - isPending: false, - allKnowledge: [], - }; - } - - const knowledge_data_key = getKnowledgeDataKey(brainId); - - const invalidateKnowledgeDataKey = () => { - void queryClient.invalidateQueries({ queryKey: [knowledge_data_key] }); - }; - - return { - invalidateKnowledgeDataKey, - isPending, - allKnowledge: allKnowledge ?? [], - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/BrainUsers.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/BrainUsers.tsx deleted file mode 100644 index 2bf8fa1fd..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/BrainUsers.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { UUID } from "crypto"; - -import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; - -import { BrainUser } from "./components/BrainUser/BrainUser"; -import { useBrainUsers } from "./hooks/useBrainUsers"; - -type BrainUsersProps = { - brainId: UUID; -}; -export const BrainUsers = ({ brainId }: BrainUsersProps): JSX.Element => { - const { brainUsers, fetchBrainUsers } = useBrainUsers(brainId); - - if (brainUsers.length === 0) { - return ( - - You are the only user to have access to this brain. - - ); - } - - return ( - <> - {brainUsers.map((subscription) => ( - - ))} - - ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/components/BrainUser/BrainUser.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/components/BrainUser/BrainUser.tsx deleted file mode 100644 index 136c2a7f2..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/components/BrainUser/BrainUser.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Select } from "@/lib/components/ui/Select"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { RemoveAccessIcon } from "./components/RemoveAccessIcon"; -import { useBrainUser } from "./hooks/useBrainUser"; - -import { availableRoles, BrainRoleType } from "../../types"; - -type BrainUserProps = { - email: string; - role: BrainRoleType; - brainId: string; - fetchBrainUsers: () => Promise; -}; - -export const BrainUser = ({ - email, - role, - brainId, - fetchBrainUsers, -}: BrainUserProps): JSX.Element => { - const { - isRemovingAccess, - canRemoveAccess, - selectedRole, - removeUserAccess, - updateSelectedRole, - } = useBrainUser({ - fetchBrainUsers: fetchBrainUsers, - role, - brainId, - email, - }); - const { currentBrain } = useBrainContext(); - - return ( -
- {canRemoveAccess && ( - void removeUserAccess()} - /> - )} -
- -
- - {maxTokens} -
- -
- ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/Prompt/Prompt.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/Prompt/Prompt.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/Prompt/Prompt.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/Prompt/Prompt.tsx deleted file mode 100644 index 5b83b7e24..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/Prompt/Prompt.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Controller } from "react-hook-form"; - -import { FieldHeader } from "@/lib/components/ui/FieldHeader/FieldHeader"; -import { TextAreaInput } from "@/lib/components/ui/TextAreaInput/TextAreaInput"; - -import styles from "./Prompt.module.scss"; - -export const Prompt = (): JSX.Element => { - return ( -
-
- - ( - - )} - /> -
-
- ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.module.scss deleted file mode 100644 index 7b050f096..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; - -.selector_wrapper { - width: 300px; - - @media (max-width: ScreenSizes.$small) { - width: 100%; - } -} diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.tsx deleted file mode 100644 index 54fe28b8e..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPrompts.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import styles from "./PublicPrompts.module.scss"; -import { PublicPromptsList } from "./PublicPromptsList/PublicPromptsList"; -import { usePublicPrompts } from "./hooks/usePublicPrompts"; - -type PublicPromptsProps = { - onSelect: ({ title, content }: { title: string; content: string }) => void; -}; - -export const PublicPrompts = ({ - onSelect, -}: PublicPromptsProps): JSX.Element => { - const { handleChange, publicPrompts } = usePublicPrompts({ - onSelect, - }); - - return ( -
- -
- ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/PublicPromptsList.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/PublicPromptsList.tsx deleted file mode 100644 index 4d6de24cb..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/PublicPromptsList.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { ChangeEvent } from "react"; - -import { SingleSelector } from "@/lib/components/ui/SingleSelector/SingleSelector"; -import { Prompt } from "@/lib/types/Prompt"; - -import { usePublicPromptsList } from "./hooks/usePublicPromptsList"; - -type PublicPromptsListProps = { - options: Prompt[]; - onChange: (event: ChangeEvent) => void; - onSelect: ({ title, content }: { title: string; content: string }) => void; -}; - -export const PublicPromptsList = ({ - options, - onChange, - onSelect, -}: PublicPromptsListProps): JSX.Element => { - const { handleOptionClick, selectedOption } = usePublicPromptsList({ - onChange, - onSelect, - }); - - const formattedOptions = options.map((option) => { - return { label: option.title, value: option.id }; - }); - - return ( - { - const findedOption = options.find( - (option) => option.id === clickedOption - ); - if (findedOption) { - handleOptionClick(findedOption); - } - }} - /> - ); -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/hooks/usePublicPromptsList.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/hooks/usePublicPromptsList.ts deleted file mode 100644 index d45a28680..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/PublicPromptsList/hooks/usePublicPromptsList.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ChangeEvent, useEffect, useRef, useState } from "react"; - -import { Prompt } from "@/lib/types/Prompt"; - -type UsePublicPromptsListProps = { - onChange: (event: ChangeEvent) => void; - onSelect: ({ title, content }: { title: string; content: string }) => void; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePublicPromptsList = ({ - onChange, - onSelect, -}: UsePublicPromptsListProps) => { - const [isOpen, setIsOpen] = useState(false); - const [selectedOption, setSelectedOption] = useState(null); - const selectRef = useRef(null); - - const toggleDropdown = () => { - setIsOpen((prevIsOpen) => !prevIsOpen); - }; - - const handleOptionClick = (option: Prompt) => { - setSelectedOption(option); - setIsOpen(false); - onChange({ - target: { value: option.id }, - } as ChangeEvent); - onSelect({ - title: option.title, - content: option.content, - }); - }; - - const handleClickOutside = (event: MouseEvent) => { - if ( - selectRef.current && - !selectRef.current.contains(event.target as Node) - ) { - setIsOpen(false); - } - }; - - useEffect(() => { - document.addEventListener("click", handleClickOutside, true); - - return () => { - document.removeEventListener("click", handleClickOutside, true); - }; - }, []); - - return { - isOpen, - selectedOption, - selectRef, - toggleDropdown, - handleOptionClick, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/hooks/usePublicPrompts.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/hooks/usePublicPrompts.ts deleted file mode 100644 index 14edab8ec..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/components/PublicPrompts/hooks/usePublicPrompts.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ChangeEvent, useEffect, useState } from "react"; - -import { usePromptApi } from "@/lib/api/prompt/usePromptApi"; -import { Prompt } from "@/lib/types/Prompt"; - -type UsePublicPromptsProps = { - onSelect: ({ title, content }: { title: string; content: string }) => void; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePublicPrompts = ({ onSelect }: UsePublicPromptsProps) => { - const [publicPrompts, setPublicPrompts] = useState([]); - const { getPublicPrompts } = usePromptApi(); - - useEffect(() => { - const fetchPublicPrompts = async () => { - setPublicPrompts(await getPublicPrompts()); - }; - void fetchPublicPrompts(); - }, []); - - const handleChange = (event: ChangeEvent) => { - const selectedPrompt = publicPrompts.find( - (prompt) => prompt.id === event.target.value - ); - if (selectedPrompt) { - onSelect({ - title: selectedPrompt.title, - content: selectedPrompt.content, - }); - } - }; - - return { - publicPrompts, - handleChange, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useBrainFormState.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useBrainFormState.ts deleted file mode 100644 index 78eedf9dd..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useBrainFormState.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-disable complexity */ - -import { useCallback, useEffect } from "react"; -import { useFormContext } from "react-hook-form"; - -import { Brain } from "@/lib/context/BrainProvider/types"; -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; -import { BrainConfig, Model } from "@/lib/types/BrainConfig"; - -import { useBrainFetcher } from "../../../hooks/useBrainFetcher"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainFormState = () => { - const { brainId } = useUrlBrain(); - - const { - register, - getValues, - watch, - setValue, - reset, - resetField, - formState: { defaultValues, dirtyFields }, - } = useFormContext(); - - const { brain, refetchBrain } = useBrainFetcher({ - brainId, - }); - - const promptId = watch("prompt_id"); - const openAiKey = watch("openAiKey"); - const model = watch("model"); - const temperature = watch("temperature"); - const maxTokens = watch("maxTokens"); - const status = watch("status"); - - const updateFormValues = useCallback(() => { - if (brain === undefined) { - return; - } - - for (const key in brain) { - const brainKey = key as keyof Brain; - if (!(key in brain)) { - return; - } - - if (brainKey === "max_tokens" && brain["max_tokens"] !== undefined) { - setValue("maxTokens", brain["max_tokens"]); - continue; - } - - // @ts-expect-error bad type inference from typescript - // eslint-disable-next-line - if (Boolean(brain[key])) setValue(key, brain[key]); - } - - setTimeout(() => { - if (brain.model) { - setValue("model", brain.model); - } - }, 50); - }, [brain, setValue]); - - useEffect(() => { - updateFormValues(); - }, [brain, updateFormValues]); - - const setModel = (newModel: Model) => { - setValue("model", newModel); - }; - - return { - brain, - brainId, - model, - temperature, - maxTokens, - promptId, - openAiKey, - defaultValues, - dirtyFields, - status, - register, - getValues, - setValue, - reset, - resetField, - refetchBrain, - setModel, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePermissionsController.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePermissionsController.ts deleted file mode 100644 index b892366e4..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePermissionsController.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { UUID } from "crypto"; -import { useEffect } from "react"; -import { useFormContext } from "react-hook-form"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { getBrainPermissions } from "../../../utils/getBrainPermissions"; - -type UsePermissionsControllerProps = { - brainId: UUID; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePermissionsController = ({ - brainId, -}: UsePermissionsControllerProps) => { - const { allBrains } = useBrainContext(); - - const { setValue } = useFormContext<{ - isApiDefinitionReadOnly: boolean; - isUpdatingApiDefinition: boolean; - }>(); - - const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({ - brainId, - userAccessibleBrains: allBrains, - }); - - useEffect(() => { - setValue("isApiDefinitionReadOnly", !hasEditRights); - setValue("isUpdatingApiDefinition", true); - }, [hasEditRights, setValue]); - - return { - hasEditRights, - isOwnedByCurrentUser, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePrompt.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePrompt.ts deleted file mode 100644 index d5169e364..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/usePrompt.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* eslint-disable max-lines */ -import { isAxiosError } from "axios"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useBrainApi } from "@/lib/api/brain/useBrainApi"; -import { usePromptApi } from "@/lib/api/prompt/usePromptApi"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useToast } from "@/lib/hooks"; - -import { useBrainFormState } from "./useBrainFormState"; - -export type UsePromptProps = { - setIsUpdating: (isUpdating: boolean) => void; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePrompt = (props: UsePromptProps) => { - const { t } = useTranslation(["translation", "brain", "config"]); - const { publish } = useToast(); - const { updateBrain } = useBrainApi(); - const { getPrompt, updatePrompt, createPrompt } = usePromptApi(); - const [isRemovingPrompt, setIsRemovingPrompt] = useState(false); - const { fetchAllBrains } = useBrainContext(); - const { - dirtyFields, - getValues, - register, - reset, - setValue, - resetField, - promptId, - refetchBrain, - brainId, - } = useBrainFormState(); - - const { setIsUpdating } = props; - - const [currentPromptId, setCurrentPromptId] = useState( - promptId - ); - - useEffect(() => { - setCurrentPromptId(promptId); - }, [promptId]); - - const fetchPrompt = async () => { - if (currentPromptId === "" || currentPromptId === undefined) { - return; - } - - const prompt = await getPrompt(currentPromptId); - if (prompt === undefined) { - return; - } - setValue("prompt", prompt); - }; - useEffect(() => { - void fetchPrompt(); - }, [currentPromptId]); - - const removeBrainPrompt = async () => { - if (brainId === undefined) { - return; - } - try { - setIsRemovingPrompt(true); - await updateBrain(brainId, { - prompt_id: null, - }); - setValue("prompt", { - title: "", - content: "", - }); - reset(); - refetchBrain(); - setCurrentPromptId(undefined); - } catch (err) { - publish({ - variant: "danger", - text: t("errorRemovingPrompt", { ns: "config" }), - }); - } finally { - setIsRemovingPrompt(false); - } - }; - - const promptHandler = async () => { - const { prompt } = getValues(); - - if (dirtyFields["prompt"] && promptId !== undefined) { - await updatePrompt(promptId, { - title: prompt.title, - content: prompt.content, - }); - } - }; - - // eslint-disable-next-line complexity - const submitPrompt = async () => { - const { prompt, maxTokens: max_tokens, ...otherConfigs } = getValues(); - - if (!dirtyFields["prompt"]) { - return; - } - - if (!prompt.title) { - prompt.title = "Untitled"; - } - - if (prompt.content === "") { - return; - } - - if (brainId === undefined) { - return; - } - - try { - if (promptId === "" || promptId === undefined) { - otherConfigs["prompt_id"] = ( - await createPrompt({ - title: prompt.title, - content: prompt.content, - }) - ).id; - - await updateBrain(brainId, { - ...otherConfigs, - max_tokens, - }); - refetchBrain(); - } else { - await Promise.all([ - updateBrain(brainId, { - ...otherConfigs, - max_tokens, - }), - promptHandler(), - ]); - } - void fetchAllBrains(); - } catch (err) { - if (isAxiosError(err) && err.response?.status === 429) { - publish({ - variant: "danger", - text: `${JSON.stringify( - ( - err.response as { - data: { detail: string }; - } - ).data.detail - )}`, - }); - } else { - publish({ - variant: "danger", - text: `${JSON.stringify(err)}`, - }); - } - } finally { - setIsUpdating(false); - } - }; - - const pickPublicPrompt = ({ - title, - content, - }: { - title: string; - content: string; - }): void => { - setValue("prompt.title", title, { - shouldDirty: true, - }); - setValue("prompt.content", content, { - shouldDirty: true, - }); - }; - - return { - register, - pickPublicPrompt, - submitPrompt, - removeBrainPrompt, - setValue, - isRemovingPrompt, - promptId, - dirtyFields, - resetField, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts deleted file mode 100644 index 18de71f5b..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { isAxiosError } from "axios"; -import { UUID } from "crypto"; -import { useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useBrainApi } from "@/lib/api/brain/useBrainApi"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { defineMaxTokens } from "@/lib/helpers/defineMaxTokens"; -import { getAccessibleModels } from "@/lib/helpers/getAccessibleModels"; -import { useToast } from "@/lib/hooks"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import { useBrainFormState } from "./useBrainFormState"; - -import { isBrainDescriptionValid } from "../utils/isBrainDescriptionValid"; -import { isBrainNameValid } from "../utils/isBrainNameValid"; - -type UseSettingsTabProps = { - brainId: UUID; - initialColor?: string; - initialEmoji?: string; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useSettingsTab = ({ - brainId, - initialColor, - initialEmoji, -}: UseSettingsTabProps) => { - const { t } = useTranslation(["translation", "brain", "config"]); - const [isUpdating, setIsUpdating] = useState(false); - const { publish } = useToast(); - const formRef = useRef(null); - const { updateBrain } = useBrainApi(); - const { fetchAllBrains } = useBrainContext(); - const { userData } = useUserData(); - - const { getValues, maxTokens, setValue, openAiKey, model } = - useBrainFormState(); - - const accessibleModels = getAccessibleModels({ - openAiKey, - userData, - }); - - useEffect(() => { - setValue("maxTokens", Math.min(maxTokens, defineMaxTokens(model))); - }, [maxTokens, model, setValue]); - - useEffect(() => { - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - void handleSubmit(); - } - }; - - formRef.current?.addEventListener("keydown", handleKeyPress); - - return () => { - formRef.current?.removeEventListener("keydown", handleKeyPress); - }; - }, [formRef.current]); - - const handleSubmit = async (color?: string, emoji?: string) => { - const { name, description } = getValues(); - - if ( - !isBrainNameValid(name, publish, t) || - !isBrainDescriptionValid(description, publish, t) - ) { - return; - } - - try { - setIsUpdating(true); - const { maxTokens: max_tokens, ...otherConfigs } = getValues(); - - await updateBrain(brainId, { - ...otherConfigs, - max_tokens, - prompt_id: - otherConfigs["prompt_id"] !== "" - ? otherConfigs["prompt_id"] - : undefined, - snippet_color: color ?? initialColor, - snippet_emoji: emoji ?? initialEmoji, - }); - - publish({ - variant: "success", - text: t("brainUpdated", { ns: "config" }), - }); - void fetchAllBrains(); - } catch (err) { - if (isAxiosError(err) && err.response?.status === 429) { - publish({ - variant: "danger", - text: `${JSON.stringify( - ( - err.response as { - data: { detail: string }; - } - ).data.detail - )}`, - }); - } else { - publish({ - variant: "danger", - text: `${JSON.stringify(err)}`, - }); - } - } finally { - setIsUpdating(false); - } - }; - - return { - handleSubmit, - isUpdating, - formRef, - accessibleModels, - setIsUpdating, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainDescriptionValid.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainDescriptionValid.ts deleted file mode 100644 index 84e7f953e..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainDescriptionValid.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TFunction } from "i18next"; - -import { ToastData } from "@/lib/components/ui/Toast/domain/types"; - -export const isBrainDescriptionValid = ( - description: string, - publish: (toast: ToastData) => void, - t: TFunction<["translation", "brain", "config"]> -): boolean => { - if (description.trim() === "") { - publish({ - variant: "danger", - text: t("descriptionRequired", { ns: "config" }), - }); - - return false; - } - - return true; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainNameValid.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainNameValid.ts deleted file mode 100644 index b0603b823..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/SettingsTab/utils/isBrainNameValid.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TFunction } from "i18next"; - -import { ToastData } from "@/lib/components/ui/Toast/domain/types"; - -export const isBrainNameValid = ( - name: string, - publish: (toast: ToastData) => void, - t: TFunction<["translation", "brain", "config"]> -): boolean => { - if (name.trim() === "") { - publish({ - variant: "danger", - text: t("nameRequired", { ns: "config" }), - }); - - return false; - } - - return true; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher.ts deleted file mode 100644 index 4f5a128e7..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { UUID } from "crypto"; -import { useRouter } from "next/navigation"; - -import { getBrainDataKey } from "@/lib/api/brain/config"; -import { useBrainApi } from "@/lib/api/brain/useBrainApi"; - -type UseBrainFetcherProps = { - brainId?: UUID; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainFetcher = ({ brainId }: UseBrainFetcherProps) => { - const { getBrain } = useBrainApi(); - const queryClient = useQueryClient(); - const router = useRouter(); - - const fetchBrain = async () => { - try { - if (brainId === undefined) { - return undefined; - } - - return await getBrain(brainId); - } catch (error) { - router.push("/studio"); - } - }; - - const { data: brain, isLoading } = useQuery({ - queryKey: brainId ? [getBrainDataKey(brainId)] : [], - queryFn: fetchBrain, - enabled: brainId !== undefined, - }); - - const invalidateBrainQuery = () => { - void queryClient.invalidateQueries({ - queryKey: brainId ? [getBrainDataKey(brainId)] : [], - }); - }; - - return { - brain, - refetchBrain: invalidateBrainQuery, - isLoading, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainManagementTabs.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainManagementTabs.ts deleted file mode 100644 index 2f71d7b85..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainManagementTabs.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { UUID } from "crypto"; -import { useParams, usePathname, useRouter } from "next/navigation"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useSubscriptionApi } from "@/lib/api/subscription/useSubscriptionApi"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useToast } from "@/lib/hooks"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -import { getBrainPermissions } from "../utils/getBrainPermissions"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainManagementTabs = (customBrainId?: UUID) => { - const { allBrains } = useBrainContext(); - const [ - isDeleteOrUnsubscribeRequestPending, - setIsDeleteOrUnsubscribeRequestPending, - ] = useState(false); - - const { track } = useEventTracking(); - const { publish } = useToast(); - - const { unsubscribeFromBrain } = useSubscriptionApi(); - const { deleteBrain, setCurrentBrainId, fetchAllBrains } = useBrainContext(); - const [ - isDeleteOrUnsubscribeModalOpened, - setIsDeleteOrUnsubscribeModalOpened, - ] = useState(false); - const router = useRouter(); - - const params = useParams(); - const pathname = usePathname(); - const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]); - const brainId = customBrainId ?? (params?.brainId as UUID | undefined); - - const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({ - brainId, - userAccessibleBrains: allBrains, - }); - - const handleUnSubscription = async () => { - if (brainId === undefined) { - return; - } - await unsubscribeFromBrain(brainId); - - void track("UNSUBSCRIBE_FROM_BRAIN"); - publish({ - variant: "success", - text: t("successfully_unsubscribed"), - }); - }; - - const handleUnsubscribeOrDeleteBrain = async () => { - if (brainId === undefined) { - return; - } - - setIsDeleteOrUnsubscribeRequestPending(true); - try { - if (!isOwnedByCurrentUser) { - await handleUnSubscription(); - } else { - await deleteBrain(brainId); - } - setCurrentBrainId(null); - setIsDeleteOrUnsubscribeModalOpened(false); - void fetchAllBrains(); - } catch (error) { - console.error("Error deleting brain: ", error); - } finally { - if (pathname !== "studio") { - router.push("/studio"); - } - void fetchAllBrains(); - setIsDeleteOrUnsubscribeRequestPending(false); - } - }; - - return { - brainId, - handleUnsubscribeOrDeleteBrain, - isDeleteOrUnsubscribeModalOpened, - setIsDeleteOrUnsubscribeModalOpened, - hasEditRights, - isOwnedByCurrentUser, - isDeleteOrUnsubscribeRequestPending, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/getBrainPermissions.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/utils/getBrainPermissions.ts deleted file mode 100644 index b8a9adf68..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/getBrainPermissions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { UUID } from "crypto"; - -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -import { isUserBrainOwner } from "./isUserBrainEditor"; -import { isUserBrainEditor } from "./isUserBrainOwner"; - -type GetBrainPermissionsProps = { - brainId?: UUID; - userAccessibleBrains: MinimalBrainForUser[]; -}; - -export const getBrainPermissions = ({ - brainId, - userAccessibleBrains, -}: GetBrainPermissionsProps): { - hasEditRights: boolean; - isOwnedByCurrentUser: boolean; -} => { - const isOwnedByCurrentUser = isUserBrainOwner({ - brainId, - userAccessibleBrains, - }); - - const userHasBrainEditorRights = isUserBrainEditor({ - brainId, - userAccessibleBrains, - }); - - const hasEditRights = isOwnedByCurrentUser || userHasBrainEditorRights; - - return { - hasEditRights, - isOwnedByCurrentUser, - }; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainEditor.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainEditor.ts deleted file mode 100644 index c1bdc5d70..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainEditor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UUID } from "crypto"; - -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -type IsUserBrainOwnerProps = { - userAccessibleBrains: MinimalBrainForUser[]; - brainId?: UUID; -}; -export const isUserBrainOwner = ({ - brainId, - userAccessibleBrains, -}: IsUserBrainOwnerProps): boolean => { - const brain = userAccessibleBrains.find(({ id }) => id === brainId); - if (brain === undefined) { - return false; - } - - return brain.role === "Owner"; -}; diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainOwner.ts b/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainOwner.ts deleted file mode 100644 index c89f1c66a..000000000 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/utils/isUserBrainOwner.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UUID } from "crypto"; - -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -type IsUserBrainOwnerProps = { - userAccessibleBrains: MinimalBrainForUser[]; - brainId?: UUID; -}; -export const isUserBrainEditor = ({ - brainId, - userAccessibleBrains, -}: IsUserBrainOwnerProps): boolean => { - const brain = userAccessibleBrains.find(({ id }) => id === brainId); - if (brain === undefined) { - return false; - } - - return brain.role === "Editor"; -}; diff --git a/frontend/app/studio/[brainId]/hooks/useBrainManagement.ts b/frontend/app/studio/[brainId]/hooks/useBrainManagement.ts deleted file mode 100644 index a6b039d12..000000000 --- a/frontend/app/studio/[brainId]/hooks/useBrainManagement.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainManagement = () => { - const { brain } = useUrlBrain(); - - return { - brain, - }; -}; diff --git a/frontend/app/studio/[brainId]/page.module.scss b/frontend/app/studio/[brainId]/page.module.scss deleted file mode 100644 index 2c50d8d85..000000000 --- a/frontend/app/studio/[brainId]/page.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.brain_management_wrapper { - width: 100%; - height: 100vh; - - .content_wrapper { - padding-block: Spacings.$spacing05; - padding-inline: Spacings.$spacing09; - } -} diff --git a/frontend/app/studio/[brainId]/page.tsx b/frontend/app/studio/[brainId]/page.tsx deleted file mode 100644 index 93d28e353..000000000 --- a/frontend/app/studio/[brainId]/page.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { useEffect } from "react"; - -import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; -import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; -import { ButtonType } from "@/lib/types/QuivrButton"; - -import { BrainManagementTabs } from "./BrainManagementTabs/BrainManagementTabs"; -import { DeleteOrUnsubscribeConfirmationModal } from "./BrainManagementTabs/components/DeleteOrUnsubscribeModal/DeleteOrUnsubscribeConfirmationModal"; -import { useBrainManagementTabs } from "./BrainManagementTabs/hooks/useBrainManagementTabs"; -import { getBrainPermissions } from "./BrainManagementTabs/utils/getBrainPermissions"; -import { useBrainManagement } from "./hooks/useBrainManagement"; -import styles from "./page.module.scss"; - -const BrainsManagement = (): JSX.Element => { - const { brain } = useBrainManagement(); - const { setIsVisible } = useSearchModalContext(); - const { - handleUnsubscribeOrDeleteBrain, - isDeleteOrUnsubscribeModalOpened, - setIsDeleteOrUnsubscribeModalOpened, - isDeleteOrUnsubscribeRequestPending, - } = useBrainManagementTabs(brain?.id); - const { allBrains } = useBrainContext(); - const { isOwnedByCurrentUser } = getBrainPermissions({ - brainId: brain?.id, - userAccessibleBrains: allBrains, - }); - const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); - const { setCurrentBrainId } = useBrainContext(); - - const buttons: ButtonType[] = [ - { - label: "Talk to Brain", - color: "primary", - onClick: () => { - if (brain) { - setIsVisible(true); - setTimeout(() => setCurrentBrainId(brain.id)); - } - }, - iconName: "chat", - }, - { - label: "Add knowledge", - color: "primary", - onClick: () => { - setShouldDisplayFeedCard(true); - }, - iconName: "uploadFile", - hidden: !isOwnedByCurrentUser || !brain?.max_files, - }, - { - label: isOwnedByCurrentUser ? "Delete Brain" : "Unsubscribe from Brain", - color: "dangerous", - onClick: () => { - setIsDeleteOrUnsubscribeModalOpened(true); - }, - iconName: "delete", - }, - ]; - - useEffect(() => { - if (brain) { - setCurrentBrainId(brain.id); - } - }, [brain]); - - if (!brain) { - return <>; - } - - return ( - <> -
- -
- -
-
- - void handleUnsubscribeOrDeleteBrain()} - isOwnedByCurrentUser={isOwnedByCurrentUser} - isDeleteOrUnsubscribeRequestPending={ - isDeleteOrUnsubscribeRequestPending - } - /> - - ); -}; - -export default BrainsManagement; diff --git a/frontend/app/studio/[brainId]/utils/sortByName.ts b/frontend/app/studio/[brainId]/utils/sortByName.ts deleted file mode 100644 index e882779df..000000000 --- a/frontend/app/studio/[brainId]/utils/sortByName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -export const sortBrainsByName = ( - minimalBrains: MinimalBrainForUser[] -): MinimalBrainForUser[] => { - // Use the sort method to sort the array by the 'name' property - const sortedMinimalBrains = minimalBrains - .slice() - .sort((a, b) => a.name.localeCompare(b.name)); - - return sortedMinimalBrains; -}; diff --git a/frontend/app/studio/layout.tsx b/frontend/app/studio/layout.tsx deleted file mode 100644 index 38be6a105..000000000 --- a/frontend/app/studio/layout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; -import { ReactNode } from "react"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; - -interface LayoutProps { - children?: ReactNode; -} - -const Layout = ({ children }: LayoutProps): JSX.Element => { - const { session } = useSupabase(); - if (session === null) { - redirectToLogin(); - } - - return ( -
- {children} -
- ); -}; - -export default Layout; diff --git a/frontend/app/studio/page.module.scss b/frontend/app/studio/page.module.scss deleted file mode 100644 index 6d69ff8bc..000000000 --- a/frontend/app/studio/page.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.page_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - width: 100%; - height: 100vh; - overflow: hidden; - - .content_wrapper { - padding-inline: Spacings.$spacing09; - padding-block: Spacings.$spacing05; - overflow-y: auto; - } -} diff --git a/frontend/app/studio/page.tsx b/frontend/app/studio/page.tsx deleted file mode 100644 index aa2de4e99..000000000 --- a/frontend/app/studio/page.tsx +++ /dev/null @@ -1,102 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -import { AddBrainModal } from "@/lib/components/AddBrainModal"; -import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider"; -import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; -import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; -import { Tabs } from "@/lib/components/ui/Tabs/Tabs"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useUserData } from "@/lib/hooks/useUserData"; -import { ButtonType } from "@/lib/types/QuivrButton"; -import { Tab } from "@/lib/types/Tab"; - -import { Analytics } from "./BrainsTabs/components/Analytics/Analytics"; -import { ManageBrains } from "./BrainsTabs/components/ManageBrains/ManageBrains"; -import styles from "./page.module.scss"; - -const Studio = (): JSX.Element => { - const [selectedTab, setSelectedTab] = useState("Manage my brains"); - const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); - const { setIsBrainCreationModalOpened } = useBrainCreationContext(); - const { allBrains } = useBrainContext(); - const { userData } = useUserData(); - - const studioTabs: Tab[] = [ - { - label: "Manage my brains", - isSelected: selectedTab === "Manage my brains", - onClick: () => setSelectedTab("Manage my brains"), - iconName: "edit", - }, - { - label: "Analytics", - isSelected: selectedTab === "Analytics", - onClick: () => setSelectedTab("Analytics"), - iconName: "graph", - }, - ]; - - const [buttons, setButtons] = useState([ - { - label: "Create brain", - color: "primary", - onClick: () => { - setIsBrainCreationModalOpened(true); - }, - iconName: "brain", - tooltip: - "You have reached the maximum number of brains allowed. Please upgrade your plan or delete some brains to create a new one.", - }, - { - label: "Add knowledge", - color: "primary", - onClick: () => { - setShouldDisplayFeedCard(true); - }, - iconName: "uploadFile", - }, - ]); - - useEffect(() => { - if (userData) { - setButtons((prevButtons) => { - return prevButtons.map((button) => { - if (button.label === "Create brain") { - return { - ...button, - disabled: - userData.max_brains <= - allBrains.filter((brain) => brain.brain_type === "doc").length, - }; - } - - return button; - }); - }); - } - }, [userData?.max_brains, allBrains.length]); - - return ( -
-
- -
-
- - {selectedTab === "Manage my brains" && } - {selectedTab === "Analytics" && } -
- - -
- ); -}; - -export default Studio; diff --git a/frontend/app/user/components/BrainConsumption.tsx b/frontend/app/user/components/BrainConsumption.tsx deleted file mode 100644 index ba05acf61..000000000 --- a/frontend/app/user/components/BrainConsumption.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { GiBrain } from "react-icons/gi"; - -import { UserStats } from "../../../lib/types/User"; - -export const BrainConsumption = (userStats: UserStats): JSX.Element => { - const { current_brain_size, max_brain_size } = userStats; - const brainFilling = current_brain_size / max_brain_size; - const { t } = useTranslation(["translation", "user"]); - - const backgroundIcon = ( - - ); - - const fillingIcon = ( - - ); - - return ( -
-
- {backgroundIcon} - {fillingIcon} -
-
- - {/* Percentage of brain space left */} - {(100 - brainFilling * 100).toFixed(2)}%{" "} - - {t("empty", { ns: "user" })} -
-
- ); -}; diff --git a/frontend/app/user/components/Connections/Connections.module.scss b/frontend/app/user/components/Connections/Connections.module.scss deleted file mode 100644 index 3544f1531..000000000 --- a/frontend/app/user/components/Connections/Connections.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.connections_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - padding-block: Spacings.$spacing06; - flex-direction: column; - width: 100%; - - .title { - @include Typography.H2; - } -} diff --git a/frontend/app/user/components/Connections/Connections.tsx b/frontend/app/user/components/Connections/Connections.tsx deleted file mode 100644 index 59fa09aaf..000000000 --- a/frontend/app/user/components/Connections/Connections.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ConnectionCards } from "@/lib/components/ConnectionCards/ConnectionCards"; - -import styles from "./Connections.module.scss"; - -export const Connections = (): JSX.Element => { - return ( -
- Link apps you want to search across - -
- ); -}; diff --git a/frontend/app/user/components/Date.tsx b/frontend/app/user/components/Date.tsx deleted file mode 100644 index 23a8d7201..000000000 --- a/frontend/app/user/components/Date.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { HTMLAttributes } from "react"; - -import { UserStats } from "../../../lib/types/User"; - -interface DateComponentProps extends HTMLAttributes { - date: UserStats["date"]; -} - -export const DateComponent = ({ - date, - ...props -}: DateComponentProps): JSX.Element => { - // Extract year, month, and day from the date string - const year = date.slice(0, 4); - const month = date.slice(4, 6); - const day = date.slice(6, 8); - - const formattedDate = new Date( - `${year}-${month}-${day}` - ).toLocaleDateString(); - - return {formattedDate}; -}; diff --git a/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.module.scss b/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.module.scss deleted file mode 100644 index 32eb377c5..000000000 --- a/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.response_wrapper { - display: flex; - gap: Spacings.$spacing03; -} diff --git a/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.tsx b/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.tsx deleted file mode 100644 index 76c062a87..000000000 --- a/frontend/app/user/components/Settings/ApiKeyConfig/ApiKeyConfig.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable max-lines */ -"use client"; - -import { CopyButton } from "@/lib/components/ui/CopyButton"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; - -import styles from "./ApiKeyConfig.module.scss"; -import { useApiKeyConfig } from "./hooks/useApiKeyConfig"; - -export const ApiKeyConfig = (): JSX.Element => { - const { apiKey, handleCopyClick, handleCreateClick } = useApiKeyConfig(); - - const createNewApiKey = async () => { - await handleCreateClick(); - }; - - return ( -
- {apiKey === "" ? ( - createNewApiKey()} - small={true} - /> - ) : ( -
- {apiKey} - -
- )} -
- ); -}; diff --git a/frontend/app/user/components/Settings/ApiKeyConfig/hooks/useApiKeyConfig.ts b/frontend/app/user/components/Settings/ApiKeyConfig/hooks/useApiKeyConfig.ts deleted file mode 100644 index 3989f4cf9..000000000 --- a/frontend/app/user/components/Settings/ApiKeyConfig/hooks/useApiKeyConfig.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable max-lines */ -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; - -import { useAuthApi } from "@/lib/api/auth/useAuthApi"; -import { USER_IDENTITY_DATA_KEY } from "@/lib/api/user/config"; -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { UserIdentity } from "@/lib/api/user/user"; -import copyToClipboard from "@/lib/helpers/copyToClipboard"; -import { useToast } from "@/lib/hooks"; -import { useGAnalyticsEventTracker } from "@/services/analytics/google/useGAnalyticsEventTracker"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useApiKeyConfig = () => { - const [apiKey, setApiKey] = useState(""); - const [openAiApiKey, setOpenAiApiKey] = useState(); - const [ - changeOpenAiApiKeyRequestPending, - setChangeOpenAiApiKeyRequestPending, - ] = useState(false); - const { updateUserIdentity, getUserIdentity } = useUserApi(); - const { track } = useEventTracking(); - const { createApiKey } = useAuthApi(); - const { publish } = useToast(); - const [userIdentity, setUserIdentity] = useState(); - const queryClient = useQueryClient(); - const { data: userData } = useQuery({ - queryKey: [USER_IDENTITY_DATA_KEY], - queryFn: getUserIdentity, - }); - const { eventTracker: gaEventTracker } = useGAnalyticsEventTracker({ - category: "QUIVR_API_KEY", - }); - - useEffect(() => { - if (userData !== undefined) { - setUserIdentity(userData); - } - }, [userData]); - - const handleCreateClick = async () => { - try { - void track("CREATE_API_KEY"); - gaEventTracker?.({ action: "CREATE_API_KEY" }); - const createdApiKey = await createApiKey(); - setApiKey(createdApiKey); - } catch (error) { - console.error("Error creating API key: ", error); - } - }; - - const handleCopyClick = () => { - if (apiKey !== "") { - void track("COPY_API_KEY"); - gaEventTracker?.({ action: "COPY_API_KEY" }); - - void copyToClipboard(apiKey); - } - }; - - const changeOpenAiApiKey = async () => { - try { - setChangeOpenAiApiKeyRequestPending(true); - - await updateUserIdentity({ - username: userIdentity?.username ?? "", - onboarded: userIdentity?.onboarded ?? false, - }); - void queryClient.invalidateQueries({ - queryKey: [USER_IDENTITY_DATA_KEY], - }); - - publish({ - variant: "success", - text: "OpenAI API Key updated", - }); - } catch (error) { - console.error(error); - } finally { - setChangeOpenAiApiKeyRequestPending(false); - } - }; - - const removeOpenAiApiKey = async () => { - try { - setChangeOpenAiApiKeyRequestPending(true); - await updateUserIdentity({ - username: userIdentity?.username ?? "", - onboarded: userIdentity?.onboarded ?? false, - }); - - publish({ - variant: "success", - text: "OpenAI API Key removed", - }); - - void queryClient.invalidateQueries({ - queryKey: [USER_IDENTITY_DATA_KEY], - }); - } catch (error) { - console.error(error); - } finally { - setChangeOpenAiApiKeyRequestPending(false); - } - }; - - return { - handleCreateClick, - apiKey, - handleCopyClick, - openAiApiKey, - setOpenAiApiKey, - changeOpenAiApiKey, - changeOpenAiApiKeyRequestPending, - userIdentity, - removeOpenAiApiKey, - }; -}; diff --git a/frontend/app/user/components/Settings/ApiKeyConfig/index.ts b/frontend/app/user/components/Settings/ApiKeyConfig/index.ts deleted file mode 100644 index 785aed328..000000000 --- a/frontend/app/user/components/Settings/ApiKeyConfig/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ApiKeyConfig"; diff --git a/frontend/app/user/components/Settings/InfoSection/InfoSection.module.scss b/frontend/app/user/components/Settings/InfoSection/InfoSection.module.scss deleted file mode 100644 index e81fe5787..000000000 --- a/frontend/app/user/components/Settings/InfoSection/InfoSection.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.info_wrapper { - display: flex; - gap: Spacings.$spacing03; - border-bottom: 1px solid var(--border-2); - padding-block: Spacings.$spacing04; - position: relative; - font-size: Typography.$small; - - &.without_border { - border-bottom: none; - } - - .title_wrapper { - display: flex; - margin-top: 0; - gap: Spacings.$spacing03; - align-items: center; - align-self: flex-start; - - .title { - min-width: 140px; - max-width: 140px; - color: var(--text-4); - font-size: Typography.$small; - } - } -} diff --git a/frontend/app/user/components/Settings/InfoSection/InfoSection.tsx b/frontend/app/user/components/Settings/InfoSection/InfoSection.tsx deleted file mode 100644 index b43be2704..000000000 --- a/frontend/app/user/components/Settings/InfoSection/InfoSection.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import styles from "./InfoSection.module.scss"; - -interface InfoSectionProps { - iconName: string; - title: string; - children: React.ReactNode; - last?: boolean; -} - -export const InfoSection = ({ - iconName, - title, - children, - last, -}: InfoSectionProps): JSX.Element => ( -
-
- - {title} -
- {children} -
-); diff --git a/frontend/app/user/components/Settings/Settings.module.scss b/frontend/app/user/components/Settings/Settings.module.scss deleted file mode 100644 index d8b6b09ef..000000000 --- a/frontend/app/user/components/Settings/Settings.module.scss +++ /dev/null @@ -1,59 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.settings_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing07; - width: auto; - padding-block: Spacings.$spacing06; - max-width: 700px; - - .title { - @include Typography.H2; - } - - .infos_wrapper { - display: flex; - flex-direction: column; - - .bold { - font-weight: 550; - } - - .credits { - color: var(--gold); - font-weight: 550; - } - - .remaining_credits { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - } - - .text_and_button { - font-size: Typography.$small; - display: flex; - flex-direction: column; - gap: Spacings.$spacing04; - - .text { - .link { - font-weight: 550; - color: var(--primary-0); - - &:hover { - color: var(--primary-1); - } - } - } - - .button { - display: flex; - align-self: flex-end; - } - } - } -} diff --git a/frontend/app/user/components/Settings/Settings.tsx b/frontend/app/user/components/Settings/Settings.tsx deleted file mode 100644 index 09161f896..000000000 --- a/frontend/app/user/components/Settings/Settings.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import { ApiKeyConfig } from "./ApiKeyConfig"; -import { InfoSection } from "./InfoSection/InfoSection"; -import styles from "./Settings.module.scss"; - -import { StripePricingOrManageButton } from "../StripePricingOrManageButton"; - -const showTokensSettings = process.env.NEXT_PUBLIC_SHOW_TOKENS === "true"; - -type InfoDisplayerProps = { - email: string; - username: string; - remainingCredits: number; -}; - -export const Settings = ({ - email, - username, - remainingCredits, -}: InfoDisplayerProps): JSX.Element => { - return ( -
- - General settings and main information - -
- - {email} - - - {username} - - {!!showTokensSettings && ( - -
- {remainingCredits} - -
-
- )} - -
- - The Quivr API key is a unique identifier that allows you to access - and interact with{" "} - - Quivr's API. - - -
- -
-
-
- {!!showTokensSettings && ( - -
- - Customize your subscription to best suit your needs. By - upgrading to a premium plan, you gain access to a host of - additional benefits, including significantly more chat credits - and the ability to create more Brains. - -
- -
-
-
- )} -
-
- ); -}; diff --git a/frontend/app/user/components/StripePricingOrManageButton.tsx b/frontend/app/user/components/StripePricingOrManageButton.tsx deleted file mode 100644 index edd2a8b4c..000000000 --- a/frontend/app/user/components/StripePricingOrManageButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { StripePricingModal } from "@/lib/components/Stripe"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { useUserData } from "@/lib/hooks/useUserData"; - -const MANAGE_PLAN_URL = process.env.NEXT_PUBLIC_STRIPE_MANAGE_PLAN_URL; - -type StripePricingModalButtonProps = { - small?: boolean; -}; - -export const StripePricingOrManageButton = ({ - small = false, -}: StripePricingModalButtonProps): JSX.Element => { - const { userData } = useUserData(); - - const is_premium = userData?.is_premium ?? false; - if (is_premium) { - return ( - - - - ); - } - - return ( - - -
- } - user_email={userData?.email ?? ""} - /> - ); -}; diff --git a/frontend/app/user/components/index.ts b/frontend/app/user/components/index.ts deleted file mode 100644 index b94a3a4df..000000000 --- a/frontend/app/user/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StripePricingOrManageButton } from "./StripePricingOrManageButton"; diff --git a/frontend/app/user/components/types/types.ts b/frontend/app/user/components/types/types.ts deleted file mode 100644 index 8706b3f65..000000000 --- a/frontend/app/user/components/types/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type UserMenuCardProps = { - title: string; - subtitle: string; - iconName: string; - selected: boolean; - onClick?: () => void; -}; diff --git a/frontend/app/user/page.module.scss b/frontend/app/user/page.module.scss deleted file mode 100644 index 1a47cadfc..000000000 --- a/frontend/app/user/page.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; - -.user_page_container { - padding-inline: Spacings.$spacing09; - padding-block: Spacings.$spacing07; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - - @media (max-width: ScreenSizes.$small) { - display: flex; - flex-direction: column; - } - - .content_wrapper { - display: flex; - gap: Spacings.$spacing05; - } -} - -.modal_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - - .buttons { - display: flex; - justify-content: space-between; - } -} diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx deleted file mode 100644 index 29c9c5347..000000000 --- a/frontend/app/user/page.tsx +++ /dev/null @@ -1,161 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { Tabs } from "@/lib/components/ui/Tabs/Tabs"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useUserData } from "@/lib/hooks/useUserData"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; -import { ButtonType } from "@/lib/types/QuivrButton"; -import { Tab } from "@/lib/types/Tab"; - -import { Connections } from "./components/Connections/Connections"; -import { Settings } from "./components/Settings/Settings"; -import styles from "./page.module.scss"; - -import { useLogoutModal } from "../../lib/hooks/useLogoutModal"; - -const UserPage = (): JSX.Element => { - const { session } = useSupabase(); - const { userData, userIdentityData } = useUserData(); - const { deleteUserData, getUserCredits } = useUserApi(); - const { t } = useTranslation(["translation", "logout"]); - const [deleteAccountModalOpened, setDeleteAccountModalOpened] = - useState(false); - const { - handleLogout, - isLoggingOut, - isLogoutModalOpened, - setIsLogoutModalOpened, - } = useLogoutModal(); - const [selectedTab, setSelectedTab] = useState("General"); - const { remainingCredits, setRemainingCredits } = useUserSettingsContext(); - - useEffect(() => { - void (async () => { - const res = await getUserCredits(); - setRemainingCredits(res); - })(); - }, []); - - const buttons: ButtonType[] = [ - { - label: "Logout", - color: "dangerous", - onClick: () => { - setIsLogoutModalOpened(true); - }, - iconName: "logout", - }, - { - label: "Delete Account", - color: "dangerous", - onClick: () => { - setDeleteAccountModalOpened(true); - }, - iconName: "delete", - }, - ]; - - const studioTabs: Tab[] = [ - { - label: "General", - isSelected: selectedTab === "General", - onClick: () => setSelectedTab("General"), - iconName: "user", - }, - { - label: "Connections", - isSelected: selectedTab === "Connections", - onClick: () => setSelectedTab("Connections"), - iconName: "sync", - }, - ]; - - if (!session || !userData) { - redirectToLogin(); - } - - return ( - <> -
- -
-
- -
-
- {selectedTab === "General" && ( - - )} - {selectedTab === "Connections" && } -
-
- } - > -
-

{t("areYouSure", { ns: "logout" })}

-
- setIsLogoutModalOpened(false)} - color="primary" - label={t("cancel", { ns: "logout" })} - iconName="close" - > - void handleLogout()} - label={t("logoutButton")} - iconName="logout" - > -
-
-
- } - > -
-

Are you sure you want to delete your account ?

-
- setDeleteAccountModalOpened(false)} - color="primary" - label={t("cancel", { ns: "logout" })} - iconName="close" - > - { - void deleteUserData(); - void handleLogout(); - }} - label="Delete Account" - iconName="logout" - > -
-
-
- - ); -}; - -export default UserPage; diff --git a/frontend/e2e/index.ts b/frontend/e2e/index.ts deleted file mode 100644 index 6a55b5a23..000000000 --- a/frontend/e2e/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { test } from "@playwright/test"; - -import { chatTests } from "./tests/chat"; -import { crawlTests } from "./tests/crawl"; -import { createBrainTests } from "./tests/createBrain"; - -test.describe(createBrainTests); - -test.describe(crawlTests); - -test.describe(chatTests); diff --git a/frontend/e2e/tests/chat/chat.ts b/frontend/e2e/tests/chat/chat.ts deleted file mode 100644 index c0ce0cb68..000000000 --- a/frontend/e2e/tests/chat/chat.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test } from "@playwright/test"; - -import { testChat } from "./utils/testChat"; -import { testDeleteChats } from "./utils/testDeleteChats"; -import { testSelectBrain } from "./utils/testSelectBrain"; -import { testUnplugChat } from "./utils/testUnplugChat"; - -import { login } from "../../utils/login"; - -export const chatTests = (): void => { - test("chat", async ({ page }) => { - await login(page); - await testChat(page); - await testUnplugChat(page); - await testSelectBrain(page); - await testDeleteChats(page); - }); -}; diff --git a/frontend/e2e/tests/chat/index.ts b/frontend/e2e/tests/chat/index.ts deleted file mode 100644 index d27da0d97..000000000 --- a/frontend/e2e/tests/chat/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./chat"; diff --git a/frontend/e2e/tests/chat/utils/getEditor.ts b/frontend/e2e/tests/chat/utils/getEditor.ts deleted file mode 100644 index 33432da4e..000000000 --- a/frontend/e2e/tests/chat/utils/getEditor.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Locator, Page } from "@playwright/test"; - -export const getEditor = (page: Page): Locator => { - const chatInputEditor = page.locator('[data-testid="chat-input"]'); - const contentEditableDiv = chatInputEditor.locator( - 'div[contentEditable="true"]' - ); - - return contentEditableDiv; -}; diff --git a/frontend/e2e/tests/chat/utils/testChat.ts b/frontend/e2e/tests/chat/utils/testChat.ts deleted file mode 100644 index 27e5775dc..000000000 --- a/frontend/e2e/tests/chat/utils/testChat.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Page } from "@playwright/test"; - -import { getEditor } from "./getEditor"; - -export const testChat = async (page: Page): Promise => { - const randomMessage = Math.random().toString(36).substring(7); - - const editor = getEditor(page); - - await editor.fill(randomMessage); - - await page.getByTestId("submit-button").click(); - - await page - .getByTestId("chat-message-text") - .getByText(`${randomMessage}`) - .isVisible(); -}; diff --git a/frontend/e2e/tests/chat/utils/testDeleteChats.ts b/frontend/e2e/tests/chat/utils/testDeleteChats.ts deleted file mode 100644 index 88b633123..000000000 --- a/frontend/e2e/tests/chat/utils/testDeleteChats.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { expect, Page } from "@playwright/test"; - -export const testDeleteChats = async (page: Page): Promise => { - const deleteChatButtons = await page.getByTestId("delete-chat-button").all(); - - for (const button of deleteChatButtons) { - await button.click(); - } - - expect((await page.getByTestId("chats-list-item").all()).length === 0); -}; diff --git a/frontend/e2e/tests/chat/utils/testSelectBrain.ts b/frontend/e2e/tests/chat/utils/testSelectBrain.ts deleted file mode 100644 index 92b723f46..000000000 --- a/frontend/e2e/tests/chat/utils/testSelectBrain.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Page } from "@playwright/test"; - -import { getEditor } from "./getEditor"; - -export const testSelectBrain = async (page: Page): Promise => { - const randomMessage = Math.random().toString(36).substring(7); - - const editor = getEditor(page); - - await editor.fill("@"); - - await page.getByText("Test brain").first().click(); - - await editor.fill(randomMessage); - - await page.getByTestId("submit-button").click(); - - await page - .getByTestId("chat-message-text") - .getByText(`${randomMessage}`) - .isVisible(); - - await page.getByTestId("brain-tags").getByText("Test brain").isVisible(); -}; diff --git a/frontend/e2e/tests/chat/utils/testUnplugChat.ts b/frontend/e2e/tests/chat/utils/testUnplugChat.ts deleted file mode 100644 index 1c816a217..000000000 --- a/frontend/e2e/tests/chat/utils/testUnplugChat.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Page } from "@playwright/test"; - -import { getEditor } from "./getEditor"; - -export const testUnplugChat = async (page: Page): Promise => { - await page.getByTestId("remove-mention").click(); - await page.getByTestId("mention-input").isHidden(); - - const randomMessage = Math.random().toString(36).substring(7); - - const editor = getEditor(page); - - await editor.fill(randomMessage); - - await page.getByTestId("submit-button").click(); - - await page - .getByTestId("chat-message-text") - .getByText(`${randomMessage}`) - .isVisible(); -}; diff --git a/frontend/e2e/tests/crawl.ts b/frontend/e2e/tests/crawl.ts deleted file mode 100644 index 82e878356..000000000 --- a/frontend/e2e/tests/crawl.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test } from "@playwright/test"; - -import { login } from "../utils/login"; - -export const crawlTests = (): void => { - test("it should be able to add url to crawl", async ({ page }) => { - await login(page); - await page.getByTestId("feed-button").click(); - await page.getByTestId("feed-card").isVisible(); - await page.getByTestId("urlToCrawlInput").click(); - await page.getByTestId("urlToCrawlInput").fill("https://quivr.app"); - await page.getByTestId("urlToCrawlInput").press("Enter"); - await page.getByTestId("urlToCrawlInput").fill("https://google.fr"); - await page.getByTestId("urlToCrawlInputSubmit").click(); - await page.getByTestId("submit-feed-button").click(); - await page.getByTestId("feed-card").isHidden(); - }); -}; diff --git a/frontend/e2e/tests/createBrain.ts b/frontend/e2e/tests/createBrain.ts deleted file mode 100644 index 29386ff38..000000000 --- a/frontend/e2e/tests/createBrain.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from "@playwright/test"; - -import { login } from "../utils/login"; - -export const createBrainTests = (): void => { - test("create brain", async ({ page }) => { - await login(page); - await page.getByTestId("brain-management-button").click(); - await page.getByTestId("add-brain-button").click(); - await page.getByTestId("brain-name").fill("Test brain"); - await page.getByTestId("create-brain-submit-button").click(); - }); -}; diff --git a/frontend/e2e/utils/login.ts b/frontend/e2e/utils/login.ts deleted file mode 100644 index b1930854f..000000000 --- a/frontend/e2e/utils/login.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Page } from "@playwright/test"; - -export const login = async (page: Page): Promise => { - const frontendUrl = process.env.NEXT_PUBLIC_E2E_URL; - const email = process.env.NEXT_PUBLIC_E2E_EMAIL; - const password = process.env.NEXT_PUBLIC_E2E_PASSWORD; - - if (frontendUrl === undefined) { - throw new Error("NEXT_PUBLIC_E2E_URL is not defined"); - } - if (email === undefined) { - throw new Error("NEXT_PUBLIC_E2E_EMAIL is not defined"); - } - if (password === undefined) { - throw new Error("NEXT_PUBLIC_E2E_PASSWORD is not defined"); - } - - await page.goto(frontendUrl); - await page.getByTestId("login-button").first().click(); - await page.getByPlaceholder("Email").fill(email); - await page.getByPlaceholder("Password").fill(password); - await page.getByTestId("submit-login").click(); - await page.getByTestId("chat-page").isVisible(); -}; diff --git a/frontend/globals.css b/frontend/globals.css deleted file mode 100644 index 6b67b040c..000000000 --- a/frontend/globals.css +++ /dev/null @@ -1,40 +0,0 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; - -main { - @apply max-w-screen-xl mx-auto flex flex-col; -} - -header, -section { - @apply px-5 md:px-10; -} - -a { - @apply hover:text-primary dark:hover:text-gray-200 transition-colors; -} - -@layer utilities { - .scrollbar::-webkit-scrollbar { - @apply w-2 h-2; - } - - .scrollbar::-webkit-scrollbar-track { - /* border-radius: 5px; */ - /* background: #fff; */ - @apply bg-white dark:bg-black; - } - - .scrollbar::-webkit-scrollbar-thumb { - /* background: #000; */ - /* border-radius: 100vh; */ - /* border: 3px solid #fff; */ - @apply bg-gray-200 dark:bg-gray-600 border border-white rounded-sm; - } - - .scrollbar::-webkit-scrollbar-thumb:hover { - /* background: #000; */ - @apply bg-gray-500; - } -} diff --git a/frontend/instrumentation.ts b/frontend/instrumentation.ts deleted file mode 100644 index 7cbe93c13..000000000 --- a/frontend/instrumentation.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from "@sentry/nextjs"; - -export async function register() { - if (process.env.NEXT_RUNTIME === "nodejs") { - await import("./sentry.server.config"); - } - - if (process.env.NEXT_RUNTIME === "edge") { - await import("./sentry.edge.config"); - } -} - -export const onRequestError = Sentry.captureRequestError; diff --git a/frontend/lib/api/analytics/analytics.ts b/frontend/lib/api/analytics/analytics.ts deleted file mode 100644 index 8d84ad51a..000000000 --- a/frontend/lib/api/analytics/analytics.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { BrainsUsages, Range } from "./types"; - -export const getBrainsUsages = async ( - axiosInstance: AxiosInstance, - brain_id: string | null, - graph_range: Range -): Promise => { - const params = { - graph_range: graph_range, - brain_id: brain_id, - }; - - const brainsUsages = ( - await axiosInstance.get( - "/analytics/brains-usages", - { params: params } - ) - ).data; - - return brainsUsages; -}; diff --git a/frontend/lib/api/analytics/types.ts b/frontend/lib/api/analytics/types.ts deleted file mode 100644 index e2015683b..000000000 --- a/frontend/lib/api/analytics/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -interface Usage { - date: Date; - usage_count: number; -} - -export interface BrainsUsages { - usages: Usage[]; -} - -export enum Range { - WEEK = 7, - MONTH = 30, - QUARTER = 90, -} diff --git a/frontend/lib/api/analytics/useAnalyticsApi.ts b/frontend/lib/api/analytics/useAnalyticsApi.ts deleted file mode 100644 index f0e1eb779..000000000 --- a/frontend/lib/api/analytics/useAnalyticsApi.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { getBrainsUsages } from "./analytics"; -import { Range } from "./types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useAnalytics = () => { - const { axiosInstance } = useAxios(); - - return { - getBrainsUsages: async (brain_id: string | null, graph_range: Range) => - getBrainsUsages(axiosInstance, brain_id, graph_range), - }; -}; diff --git a/frontend/lib/api/assistants/assistants.ts b/frontend/lib/api/assistants/assistants.ts deleted file mode 100644 index 8e02aae0d..000000000 --- a/frontend/lib/api/assistants/assistants.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { - Assistant, - ProcessAssistantInput, -} from "@/app/quality-assistant/types/assistant"; -import { Process } from "@/app/quality-assistant/types/process"; - -export const getAssistants = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/assistants`)).data; -}; - -export const getTasks = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/assistants/tasks`)).data; -}; - -export const processTask = async ( - axiosInstance: AxiosInstance, - processAssistantInput: ProcessAssistantInput -): Promise => { - const formData = new FormData(); - - formData.append("input", JSON.stringify(processAssistantInput.input)); - - processAssistantInput.files.forEach((file) => { - if (file instanceof File) { - formData.append("files", file); - } else { - console.error("L'élément n'est pas un fichier valide", file); - } - }); - - const response = await axiosInstance.post( - `/assistants/task`, - formData, - { - headers: { - "Content-Type": "multipart/form-data", - }, - } - ); - - return response.data; -}; - -export const deleteTask = async ( - axiosInstance: AxiosInstance, - taskId: number -): Promise => { - await axiosInstance.delete(`/assistants/task/${taskId}`); -}; - -export const downloadTaskResult = async ( - axiosInstance: AxiosInstance, - taskId: number -): Promise => { - return (await axiosInstance(`/assistants/task/${taskId}/download`)) - .data; -}; diff --git a/frontend/lib/api/assistants/types.ts b/frontend/lib/api/assistants/types.ts deleted file mode 100644 index 267ce4562..000000000 --- a/frontend/lib/api/assistants/types.ts +++ /dev/null @@ -1,64 +0,0 @@ -interface AssistantInput { - key: string; - required: boolean; - description: string; -} - -interface FilesInputAssistant extends AssistantInput { - allowed_extensions: string[]; -} - -export interface AssistantInputs { - files: FilesInputAssistant[]; - urls: AssistantInput[]; - texts: AssistantInput[]; -} - -interface AssistantOutput { - required: boolean; - description: string; - type: string; -} - -interface AssistantOutputs { - email: AssistantOutput; - brain: AssistantOutput; -} - -export interface Assistant { - name: string; - input_description: string; - output_description: string; - inputs: AssistantInputs; - outputs: AssistantOutputs; - tags: string[]; - icon_url: string; - description: string; -} - -export interface ProcessAssistantRequest { - name: string; - inputs: { - files: { - key: string; - value: string; - }[]; - urls: { - key: string; - value: string; - }[]; - texts: { - key: string; - value: string; - }[]; - }; - outputs: { - email: { - activated: boolean; - }; - brain: { - activated: boolean; - value: string; - }; - }; -} diff --git a/frontend/lib/api/assistants/useAssistants.ts b/frontend/lib/api/assistants/useAssistants.ts deleted file mode 100644 index 04df53d10..000000000 --- a/frontend/lib/api/assistants/useAssistants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ProcessAssistantInput } from "@/app/quality-assistant/types/assistant"; -import { useAxios } from "@/lib/hooks"; - -import { - deleteTask, - downloadTaskResult, - getAssistants, - getTasks, - processTask, -} from "./assistants"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useAssistants = () => { - const { axiosInstance } = useAxios(); - - return { - getAssistants: async () => getAssistants(axiosInstance), - getTasks: async () => getTasks(axiosInstance), - processTask: async (processAssistantInput: ProcessAssistantInput) => - processTask(axiosInstance, processAssistantInput), - deleteTask: async (taskId: number) => deleteTask(axiosInstance, taskId), - downloadTaskResult: async (taskId: number) => - downloadTaskResult(axiosInstance, taskId), - }; -}; diff --git a/frontend/lib/api/auth/auth.ts b/frontend/lib/api/auth/auth.ts deleted file mode 100644 index 4c170aa5f..000000000 --- a/frontend/lib/api/auth/auth.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AxiosInstance } from "axios"; - -export const createApiKey = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.post<{ api_key: string }>("/api-key"); - - return response.data.api_key; -}; diff --git a/frontend/lib/api/auth/useAuthApi.ts b/frontend/lib/api/auth/useAuthApi.ts deleted file mode 100644 index 68a4ed0bd..000000000 --- a/frontend/lib/api/auth/useAuthApi.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { createApiKey } from "./auth"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useAuthApi = () => { - const { axiosInstance } = useAxios(); - - return { - createApiKey: async () => createApiKey(axiosInstance), - }; -}; diff --git a/frontend/lib/api/brain/brain.ts b/frontend/lib/api/brain/brain.ts deleted file mode 100644 index bee91a5b4..000000000 --- a/frontend/lib/api/brain/brain.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* eslint-disable max-lines */ -import { AxiosInstance } from "axios"; - -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; -import { - BackendMinimalBrainForUser, - Brain, - MinimalBrainForUser, - PublicBrain, -} from "@/lib/context/BrainProvider/types"; - -import { - CreateBrainInput, - IntegrationBrains, - ListFilesProps, - SubscriptionUpdatableProperties, - UpdateBrainInput, -} from "./types"; -import { mapBackendMinimalBrainToMinimalBrain } from "./utils/mapBackendMinimalBrainToMinimalBrain"; -import { - BackendSubscription, - mapSubscriptionToBackendSubscription, -} from "./utils/mapSubscriptionToBackendSubscription"; -import { mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties } from "./utils/mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties"; - -export const createBrain = async ( - brain: CreateBrainInput, - axiosInstance: AxiosInstance -): Promise => { - return mapBackendMinimalBrainToMinimalBrain( - (await axiosInstance.post(`/brains/`, brain)) - .data - ); -}; - -export const getBrain = async ( - brainId: string, - axiosInstance: AxiosInstance -): Promise => { - const brain = ( - await axiosInstance.get(`/brains/${brainId}/`) - ).data; - - return brain; -}; - -export const deleteBrain = async ( - brainId: string, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.delete(`/brains/${brainId}/subscription`); -}; - -export const getBrains = async ( - axiosInstance: AxiosInstance -): Promise => { - const { brains } = ( - await axiosInstance.get<{ brains: BackendMinimalBrainForUser[] }>( - `/brains/` - ) - ).data; - - const sortedBrains = brains.sort((a, b) => { - if (a.brain_type === "model" && b.brain_type !== "model") { - return -1; - } else if (a.brain_type !== "model" && b.brain_type === "model") { - return 1; - } else if ( - a.brain_type === "model" && - b.brain_type === "model" && - a.display_name && - b.display_name - ) { - return a.display_name.localeCompare(b.display_name); - } else { - return a.name.localeCompare(b.name); - } - }); - - return sortedBrains.map(mapBackendMinimalBrainToMinimalBrain); -}; - -export type Subscription = { email: string; role: BrainRoleType }; - -export const addBrainSubscriptions = async ( - brainId: string, - subscriptions: Subscription[], - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.post( - `/brains/${brainId}/subscription`, - subscriptions.map(mapSubscriptionToBackendSubscription) - ); -}; - -export const getBrainUsers = async ( - brainId: string, - axiosInstance: AxiosInstance -): Promise => { - const brainsUsers = ( - await axiosInstance.get(`/brains/${brainId}/users`) - ).data; - - return brainsUsers.map((brainUser) => ({ - email: brainUser.email, - role: brainUser.rights, - })); -}; - -export const updateBrainAccess = async ( - brainId: string, - userEmail: string, - subscription: SubscriptionUpdatableProperties, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.put(`/brains/${brainId}/subscription`, { - ...mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties( - subscription - ), - email: userEmail, - }); -}; - -export const updateBrain = async ( - brainId: string, - brain: UpdateBrainInput, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.put(`/brains/${brainId}/`, brain); -}; - -export const getPublicBrains = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/brains/public`)).data; -}; - -export const updateBrainSecrets = async ( - brainId: string, - secrets: Record, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.put(`/brains/${brainId}/secrets-values`, secrets); -}; - -export const getDocsFromQuestion = async ( - brainId: string, - question: string, - axiosInstance: AxiosInstance -): Promise => { - return ( - await axiosInstance.post>( - `/brains/${brainId}/documents`, - { - question, - } - ) - ).data.docs; -}; - -export const getIntegrationBrains = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/brains/integrations/`)) - .data; -}; diff --git a/frontend/lib/api/brain/config.ts b/frontend/lib/api/brain/config.ts deleted file mode 100644 index d3da5381c..000000000 --- a/frontend/lib/api/brain/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -const BRAIN_DATA_KEY = "quivr-brains"; - -export const getBrainDataKey = (brainId: string): string => - `${BRAIN_DATA_KEY}-${brainId}`; - -export const getBrainKnowledgeDataKey = (brainId: string): string => - `${BRAIN_DATA_KEY}-${brainId}-knowledge`; - -export const PUBLIC_BRAINS_KEY = "quivr-public-brains"; diff --git a/frontend/lib/api/brain/types.ts b/frontend/lib/api/brain/types.ts deleted file mode 100644 index 3a5c86218..000000000 --- a/frontend/lib/api/brain/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { UUID } from "crypto"; - -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; -import { BrainStatus, BrainType, Model } from "@/lib/types/BrainConfig"; - -export type ApiBrainDefinitionSchemaPropertyType = "string" | "number"; - -export type ApiBrainDefinitionSchemaProperty = { - type: ApiBrainDefinitionSchemaPropertyType; - description: string; - name: string; -}; -export const allowedRequestMethods = ["GET", "POST", "PUT", "DELETE"] as const; - -export type AllowedRequestMethod = (typeof allowedRequestMethods)[number]; - -export type ApiBrainDefinitionSchema = { - properties: ApiBrainDefinitionSchemaProperty[]; - required: string[]; -}; - -export type SubscriptionUpdatableProperties = { - role: BrainRoleType | null; -}; - -export type ListFilesProps = { - files: { - file_name: string; - file_sha1: string; - file_size: number; - file_url: string; - file_id: string; - file_similarity: number; - }[]; -}; - -export type ApiBrainDefinitionSecret = { - name: string; - type: ApiBrainDefinitionSchemaPropertyType; - description: string; -}; - -export type ApiBrainDefinition = { - brain_id: UUID; - method: AllowedRequestMethod; - url: string; - search_params: ApiBrainDefinitionSchema; - params: ApiBrainDefinitionSchema; - secrets?: ApiBrainDefinitionSecret[]; - raw: boolean; - jq_instructions: string; -}; - -export type IntegrationSettings = { - integration_id?: string; - settings?: { [x: string]: string | undefined }; -}; - -export type CreateBrainInput = { - name: string; - description: string; - status?: BrainStatus; - model?: Model; - temperature?: number; - max_tokens?: number; - prompt_id?: string | null; - brain_type?: BrainType; - brain_definition?: Omit; - brain_secrets_values?: Record; - connected_brains_ids?: UUID[]; - integration?: IntegrationSettings; - snippet_color?: string; - snippet_emoji?: string; -}; - -enum IntegrationBrainTag { - NEW = "new", - RECOMMENDED = "recommended", - MOST_POPULAR = "most_popular", - PREMIUM = "premium", - COMING_SOON = "coming_soon", - COMMUNITY = "community", - DEPRECATED = "deprecated", -} - -export type IntegrationBrains = { - id: UUID; - integration_name: string; - integration_logo_url: string; - connection_settings: string; - integration_type: "custom" | "sync"; - description: string; - max_files: number; - tags: IntegrationBrainTag[]; - information: string; - integration_display_name: string; - onboarding_brain: boolean; -}; - -export type UpdateBrainInput = Partial; diff --git a/frontend/lib/api/brain/useBrainApi.ts b/frontend/lib/api/brain/useBrainApi.ts deleted file mode 100644 index 5c4f2cc5d..000000000 --- a/frontend/lib/api/brain/useBrainApi.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { - addBrainSubscriptions, - createBrain, - deleteBrain, - getBrain, - getBrains, - getBrainUsers, - getDocsFromQuestion, - getIntegrationBrains, - getPublicBrains, - Subscription, - updateBrain, - updateBrainAccess, - updateBrainSecrets, -} from "./brain"; -import { - CreateBrainInput, - SubscriptionUpdatableProperties, - UpdateBrainInput, -} from "./types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainApi = () => { - const { axiosInstance } = useAxios(); - - return { - createBrain: async (brain: CreateBrainInput) => - createBrain(brain, axiosInstance), - deleteBrain: async (id: string) => deleteBrain(id, axiosInstance), - getBrains: async () => getBrains(axiosInstance), - getBrain: async (id: string) => getBrain(id, axiosInstance), - addBrainSubscriptions: async ( - brainId: string, - subscriptions: Subscription[] - ) => addBrainSubscriptions(brainId, subscriptions, axiosInstance), - getBrainUsers: async (brainId: string) => - getBrainUsers(brainId, axiosInstance), - updateBrainAccess: async ( - brainId: string, - userEmail: string, - subscription: SubscriptionUpdatableProperties - ) => updateBrainAccess(brainId, userEmail, subscription, axiosInstance), - updateBrain: async (brainId: string, brain: UpdateBrainInput) => - updateBrain(brainId, brain, axiosInstance), - getPublicBrains: async () => getPublicBrains(axiosInstance), - getDocsFromQuestion: async (brainId: string, question: string) => - getDocsFromQuestion(brainId, question, axiosInstance), - updateBrainSecrets: async ( - brainId: string, - secrets: Record - ) => updateBrainSecrets(brainId, secrets, axiosInstance), - getIntegrationBrains: async () => getIntegrationBrains(axiosInstance), - }; -}; diff --git a/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts b/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts deleted file mode 100644 index e86746968..000000000 --- a/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - BackendMinimalBrainForUser, - MinimalBrainForUser, -} from "@/lib/context/BrainProvider/types"; - -export const mapBackendMinimalBrainToMinimalBrain = ( - backendMinimalBrain: BackendMinimalBrainForUser -): MinimalBrainForUser => ({ - id: backendMinimalBrain.id, - name: backendMinimalBrain.name, - role: backendMinimalBrain.rights, - status: backendMinimalBrain.status, - brain_type: backendMinimalBrain.brain_type, - description: backendMinimalBrain.description, - integration_logo_url: backendMinimalBrain.integration_logo_url, - max_files: backendMinimalBrain.max_files, - allow_model_change: backendMinimalBrain.allow_model_change, - image_url: backendMinimalBrain.image_url, - display_name: backendMinimalBrain.display_name, - snippet_emoji: backendMinimalBrain.snippet_emoji, - snippet_color: backendMinimalBrain.snippet_color, -}); diff --git a/frontend/lib/api/brain/utils/mapSubscriptionToBackendSubscription.ts b/frontend/lib/api/brain/utils/mapSubscriptionToBackendSubscription.ts deleted file mode 100644 index 5ed0217a2..000000000 --- a/frontend/lib/api/brain/utils/mapSubscriptionToBackendSubscription.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; - -import { Subscription } from "../brain"; - -export type BackendSubscription = { email: string; rights: BrainRoleType }; - -export const mapSubscriptionToBackendSubscription = ( - subscription: Subscription -): BackendSubscription => ({ - email: subscription.email, - rights: subscription.role, -}); diff --git a/frontend/lib/api/brain/utils/mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties.ts b/frontend/lib/api/brain/utils/mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties.ts deleted file mode 100644 index 4be9e5bf5..000000000 --- a/frontend/lib/api/brain/utils/mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; - -import { SubscriptionUpdatableProperties } from "../types"; - -type BackendSubscriptionUpdatableProperties = { - rights: BrainRoleType | null; -}; -export const mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties = - ( - subscriptionUpdatableProperties: SubscriptionUpdatableProperties - ): BackendSubscriptionUpdatableProperties => ({ - rights: subscriptionUpdatableProperties.role, - }); diff --git a/frontend/lib/api/chat/addQuestionAndAnswer.ts b/frontend/lib/api/chat/addQuestionAndAnswer.ts deleted file mode 100644 index 72f1856a1..000000000 --- a/frontend/lib/api/chat/addQuestionAndAnswer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { ChatMessage } from "@/app/chat/[chatId]/types"; - -export type QuestionAndAnwser = { - question: string; - answer: string; -}; - -export const addQuestionAndAnswer = async ( - chatId: string, - questionAndAnswer: QuestionAndAnwser, - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.post( - `/chat/${chatId}/question/answer`, - questionAndAnswer - ); - - return response.data; -}; diff --git a/frontend/lib/api/chat/chat.local.ts b/frontend/lib/api/chat/chat.local.ts deleted file mode 100644 index d07591938..000000000 --- a/frontend/lib/api/chat/chat.local.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChatConfig } from "@/lib/context/ChatProvider/types"; -const chatConfigLocalStorageKey = "chat-config"; - -type PartialChatConfig = Partial; - -export const saveChatsConfigInLocalStorage = ( - chatConfig: PartialChatConfig -): void => { - localStorage.setItem(chatConfigLocalStorageKey, JSON.stringify(chatConfig)); -}; - -export const getChatsConfigFromLocalStorage = (): - | PartialChatConfig - | undefined => { - try { - const config = localStorage.getItem(chatConfigLocalStorageKey); - - if (config === null) { - return undefined; - } - - return JSON.parse(config) as PartialChatConfig; - } catch (error) { - return undefined; - } -}; diff --git a/frontend/lib/api/chat/chat.ts b/frontend/lib/api/chat/chat.ts deleted file mode 100644 index b5c49cc72..000000000 --- a/frontend/lib/api/chat/chat.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { - ChatEntity, - ChatItem, - ChatMessage, - ChatQuestion, -} from "@/app/chat/[chatId]/types"; - -export type ChatUpdatableProperties = { - chat_name?: string; -}; - -export type ChatMessageUpdatableProperties = { - thumbs?: boolean | null; -}; - -export const createChat = async ( - name: string, - axiosInstance: AxiosInstance -): Promise => { - const createdChat = ( - await axiosInstance.post("/chat", { name: name }) - ).data; - - return createdChat; -}; - -export const getChats = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get<{ - chats: ChatEntity[]; - }>(`/chat`); - - return response.data.chats; -}; - -export const deleteChat = async ( - chatId: string, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.delete(`/chat/${chatId}`); -}; - -export type AddQuestionParams = { - chatId: string; - chatQuestion: ChatQuestion; - brainId: string; -}; - -export const addQuestion = async ( - { chatId, chatQuestion, brainId }: AddQuestionParams, - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.post( - `/chat/${chatId}/question?brain_id=${brainId}`, - chatQuestion - ); - - return response.data; -}; - -export const getChatItems = async ( - chatId: string, - axiosInstance: AxiosInstance -): Promise => - (await axiosInstance.get(`/chat/${chatId}/history`)).data; - -export const updateChat = async ( - chatId: string, - chat: ChatUpdatableProperties, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.put(`/chat/${chatId}/metadata`, chat)) - .data; -}; - -export const updateChatMessage = async ( - chatId: string, - messageId: string, - chatMessageUpdatableProperties: ChatMessageUpdatableProperties, - axiosInstance: AxiosInstance -): Promise => { - return ( - await axiosInstance.put( - `/chat/${chatId}/${messageId}`, - chatMessageUpdatableProperties - ) - ).data; -}; diff --git a/frontend/lib/api/chat/config.ts b/frontend/lib/api/chat/config.ts deleted file mode 100644 index 7b48a69a4..000000000 --- a/frontend/lib/api/chat/config.ts +++ /dev/null @@ -1 +0,0 @@ -export const CHATS_DATA_KEY = "quivr-chats"; diff --git a/frontend/lib/api/chat/useChatApi.ts b/frontend/lib/api/chat/useChatApi.ts deleted file mode 100644 index 67b3deb97..000000000 --- a/frontend/lib/api/chat/useChatApi.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { - addQuestionAndAnswer, - QuestionAndAnwser, -} from "./addQuestionAndAnswer"; -import { - addQuestion, - AddQuestionParams, - ChatMessageUpdatableProperties, - ChatUpdatableProperties, - createChat, - deleteChat, - getChatItems, - getChats, - updateChat, - updateChatMessage, -} from "./chat"; - -// TODO: split './chat.ts' into multiple files, per function for example -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatApi = () => { - const { axiosInstance } = useAxios(); - - return { - createChat: async (chatName: string) => createChat(chatName, axiosInstance), - getChats: async () => getChats(axiosInstance), - deleteChat: async (chatId: string) => deleteChat(chatId, axiosInstance), - addQuestion: async (props: AddQuestionParams) => - addQuestion(props, axiosInstance), - getChatItems: async (chatId: string) => getChatItems(chatId, axiosInstance), - updateChat: async (chatId: string, props: ChatUpdatableProperties) => - updateChat(chatId, props, axiosInstance), - addQuestionAndAnswer: async ( - chatId: string, - questionAndAnswer: QuestionAndAnwser - ) => addQuestionAndAnswer(chatId, questionAndAnswer, axiosInstance), - updateChatMessage: async ( - chatId: string, - messageId: string, - props: ChatMessageUpdatableProperties - ) => updateChatMessage(chatId, messageId, props, axiosInstance), - }; -}; diff --git a/frontend/lib/api/cms/config.ts b/frontend/lib/api/cms/config.ts deleted file mode 100644 index 58f4b8768..000000000 --- a/frontend/lib/api/cms/config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const TESTIMONIALS_DATA_KEY = "testimonials"; -export const USE_CASES_DATA_KEY = "useCases"; -export const DEMO_VIDEO_DATA_KEY = "demoVideo"; -export const SECURITY_QUESTIONS_DATA_KEY = "securityQuestions"; -export const NOTIFICATION_BANNER_DATA_KEY = "notificationBanner"; diff --git a/frontend/lib/api/cms/useCmsApi.ts b/frontend/lib/api/cms/useCmsApi.ts deleted file mode 100644 index 97c65cfcc..000000000 --- a/frontend/lib/api/cms/useCmsApi.ts +++ /dev/null @@ -1,24 +0,0 @@ -import axios from "axios"; - -import { DEFAULT_CMS_URL } from "@/lib/config/CONSTANTS"; - -import { getDemoVideoUrl } from "./utils/demoVideo"; -import { getNotificationBanner } from "./utils/notificationBanner"; -import { getSecurityQuestions } from "./utils/securityQuestion"; -import { getTestimonials } from "./utils/testimonials"; -import { getUseCases } from "./utils/useCases"; - -const axiosInstance = axios.create({ - baseURL: `${process.env.NEXT_PUBLIC_CMS_URL ?? DEFAULT_CMS_URL}`, -}); - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useCmsApi = () => { - return { - getTestimonials: () => getTestimonials(axiosInstance), - getUseCases: () => getUseCases(axiosInstance), - getDemoVideoUrl: () => getDemoVideoUrl(axiosInstance), - getSecurityQuestions: () => getSecurityQuestions(axiosInstance), - getNotificationBanner: () => getNotificationBanner(axiosInstance), - }; -}; diff --git a/frontend/lib/api/cms/utils/demoVideo.ts b/frontend/lib/api/cms/utils/demoVideo.ts deleted file mode 100644 index a5d70a812..000000000 --- a/frontend/lib/api/cms/utils/demoVideo.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AxiosInstance } from "axios"; - -type CmsVideo = { - data: { - attributes: { - Video: { - data: { - attributes: { - url: string; - }; - }; - }; - }; - }; -}; - -export const getDemoVideoUrl = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get( - "/api/demo-video?populate=Video" - ); - - return response.data.data.attributes.Video.data.attributes.url; -}; diff --git a/frontend/lib/api/cms/utils/notificationBanner.ts b/frontend/lib/api/cms/utils/notificationBanner.ts deleted file mode 100644 index cc9ae8038..000000000 --- a/frontend/lib/api/cms/utils/notificationBanner.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { NotificationBanner } from "@/lib/types/NotificationBanner"; - -type CmsNotificationBanner = { - data: { - attributes: { - text: string; - notification_id: string; - style?: Record; - dismissible?: boolean; - isSticky?: boolean; - }; - }; -}; - -const mapCmsNotificationBannerToNotificationBanner = ( - cmsNotificationBanner: CmsNotificationBanner -): NotificationBanner => ({ - text: cmsNotificationBanner.data.attributes.text, - id: cmsNotificationBanner.data.attributes.notification_id, - style: cmsNotificationBanner.data.attributes.style, - dismissible: cmsNotificationBanner.data.attributes.dismissible, - isSticky: cmsNotificationBanner.data.attributes.isSticky, -}); - -export const getNotificationBanner = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get( - "/api/notification-banner" - ); - - return mapCmsNotificationBannerToNotificationBanner(response.data); -}; diff --git a/frontend/lib/api/cms/utils/securityQuestion.ts b/frontend/lib/api/cms/utils/securityQuestion.ts deleted file mode 100644 index 584c24c0c..000000000 --- a/frontend/lib/api/cms/utils/securityQuestion.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { SecurityQuestion } from "@/lib/types/SecurityQuestion"; - -type CmsSecurityQuestions = { - data: { - id: number; - attributes: { - createdAt: string; - updatedAt: string; - publishedAt: string; - locale: string; - question: string; - answer: string; - }; - }[]; -}; - -const mapCmsSecurityQuestionToSecurityQuestion = ( - cmsSecurityQuestions: CmsSecurityQuestions -): SecurityQuestion[] => - cmsSecurityQuestions.data.map((cmsSecurityQuestion) => ({ - question: cmsSecurityQuestion.attributes.question, - answer: cmsSecurityQuestion.attributes.answer, - })); - -export const getSecurityQuestions = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get( - "/api/security-questions" - ); - - return mapCmsSecurityQuestionToSecurityQuestion(response.data); -}; diff --git a/frontend/lib/api/cms/utils/testimonials.ts b/frontend/lib/api/cms/utils/testimonials.ts deleted file mode 100644 index 0456873b5..000000000 --- a/frontend/lib/api/cms/utils/testimonials.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { Testimonial } from "@/lib/types/Testimonial"; - -type CmsTestimonials = { - data: { - id: number; - attributes: { - url: string; - jobTitle: string; - content: string; - name: string; - profilePicture: string | null; - socialMedia: string; - createdAt: string; - updatedAt: string; - publishedAt: string; - locale: string; - }; - }[]; - meta: { - pagination: { - page: number; - pageSize: number; - pageCount: number; - total: number; - }; - }; -}; - -const mapCmsTestimonialsToTestimonial = ( - testimonials: CmsTestimonials -): Testimonial[] => { - return testimonials.data.map((item) => ({ - socialMedia: item.attributes.socialMedia as "x" | "linkedin", - url: item.attributes.url, - jobTitle: item.attributes.jobTitle, - content: item.attributes.content, - name: item.attributes.name, - profilePicture: item.attributes.profilePicture ?? undefined, - })); -}; - -export const getTestimonials = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get( - "/api/testimonials" - ); - - return mapCmsTestimonialsToTestimonial(response.data); -}; diff --git a/frontend/lib/api/cms/utils/useCases.ts b/frontend/lib/api/cms/utils/useCases.ts deleted file mode 100644 index be0eec907..000000000 --- a/frontend/lib/api/cms/utils/useCases.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { UseCase } from "@/lib/types/UseCase"; - -type CmsUseCases = { - data: { - id: number; - attributes: { - description: string; - createdAt: string; - updatedAt: string; - publishedAt: string; - name: string; - locale: string; - discussions: { - data: { - id: number; - attributes: { - Question: string; - Answer: string; - createdAt: string; - updatedAt: string; - publishedAt: string; - locale: string; - }; - }[]; - }; - }; - }[]; - meta: { - pagination: { - page: number; - pageSize: number; - pageCount: number; - total: number; - }; - }; -}; - -const mapCmsUseCasesToUsecase = (jsonData: CmsUseCases): UseCase[] => { - return jsonData.data.map((item) => ({ - id: item.id.toString(), - description: item.attributes.description, - name: item.attributes.name, - createdAt: item.attributes.createdAt, - updatedAt: item.attributes.updatedAt, - publishedAt: item.attributes.publishedAt, - locale: item.attributes.locale, - discussions: item.attributes.discussions.data.map((discussion) => ({ - question: discussion.attributes.Question, - answer: discussion.attributes.Answer, - discussionCreatedAt: discussion.attributes.createdAt, - })), - })); -}; - -export const getUseCases = async ( - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get( - "/api/use-cases?populate=discussions" - ); - - return mapCmsUseCasesToUsecase(response.data); -}; diff --git a/frontend/lib/api/crawl/__tests__/useCrawlApi.test.ts b/frontend/lib/api/crawl/__tests__/useCrawlApi.test.ts deleted file mode 100644 index 24625b135..000000000 --- a/frontend/lib/api/crawl/__tests__/useCrawlApi.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { renderHook } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { CrawlInputProps } from "../crawl"; -import { useCrawlApi } from "../useCrawlApi"; - -const axiosPostMock = vi.fn(() => ({})); - -vi.mock("@/lib/hooks", () => ({ - useAxios: () => ({ - axiosInstance: { - post: axiosPostMock, - }, - }), -})); - -describe("useCrawlApi", () => { - // TODO: Create a test user within preview and prod databases to interact with - it("should call updateUserIdentity with the correct parameters", async () => { - const { - result: { - current: { crawlWebsiteUrl }, - }, - } = renderHook(() => useCrawlApi()); - const crawlInputProps: CrawlInputProps = { - brainId: "e7001ccd-6d90-4eab-8c50-2f23d39441e4", - chat_id: "e7001ccd-6d90-4eab-8c50-2f23d39441es", - config: { - url: "https://en.wikipedia.org/wiki/Mali", - js: false, - depth: 1, - max_pages: 100, - max_time: 60, - }, - }; - await crawlWebsiteUrl(crawlInputProps); - - expect(axiosPostMock).toHaveBeenCalledTimes(1); - expect(axiosPostMock).toHaveBeenCalledWith( - `/crawl?brain_id=${crawlInputProps.brainId}&chat_id=${ - crawlInputProps.chat_id ?? "" - }`, - crawlInputProps.config - ); - }); -}); diff --git a/frontend/lib/api/crawl/crawl.ts b/frontend/lib/api/crawl/crawl.ts deleted file mode 100644 index b1128acc2..000000000 --- a/frontend/lib/api/crawl/crawl.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { ToastData } from "@/lib/components/ui/Toast/domain/types"; - -export type CrawlInputProps = { - brainId: UUID; - chat_id?: UUID; - config: { - url: string; - js: boolean; - depth: number; - max_pages: number; - max_time: number; - }; - bulk_id: UUID; -}; - -export type CrawlResponse = { - data: { type: ToastData["variant"]; message: ToastData["text"] }; -}; - -export const crawlWebsiteUrl = async ( - props: CrawlInputProps, - axiosInstance: AxiosInstance -): Promise => { - let crawlUrl = `/crawl?bulk_id=${props.bulk_id}&brain_id=${props.brainId}`; - - if (props.chat_id !== undefined) { - crawlUrl = crawlUrl.concat(`&chat_id=${props.chat_id}`); - } - - return axiosInstance.post(crawlUrl, props.config); -}; diff --git a/frontend/lib/api/crawl/useCrawlApi.ts b/frontend/lib/api/crawl/useCrawlApi.ts deleted file mode 100644 index 480afb083..000000000 --- a/frontend/lib/api/crawl/useCrawlApi.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { CrawlInputProps, crawlWebsiteUrl } from "./crawl"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useCrawlApi = () => { - const { axiosInstance } = useAxios(); - - return { - crawlWebsiteUrl: async (props: CrawlInputProps) => - crawlWebsiteUrl(props, axiosInstance), - }; -}; diff --git a/frontend/lib/api/index.ts b/frontend/lib/api/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/api/knowledge/config.ts b/frontend/lib/api/knowledge/config.ts deleted file mode 100644 index 89332d8ff..000000000 --- a/frontend/lib/api/knowledge/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { UUID } from "crypto"; - -const brainDataKey = "quivr-knowledge"; - -export const getKnowledgeDataKey = (knowledgeId: UUID): string => - `${brainDataKey}-${knowledgeId}`; diff --git a/frontend/lib/api/knowledge/knowledge.ts b/frontend/lib/api/knowledge/knowledge.ts deleted file mode 100644 index 8f12d94ed..000000000 --- a/frontend/lib/api/knowledge/knowledge.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { - CrawledKnowledge, - Knowledge, - UploadedKnowledge, -} from "@/lib/types/Knowledge"; - -export type GetAllKnowledgeInputProps = { - brainId: UUID; -}; - -interface BEKnowledge { - id: UUID; - brain_id: UUID; - file_name: string | null; - url: string | null; - extension: string; - status: string; - integration: string; - integration_link: string; -} - -export const getAllKnowledge = async ( - { brainId }: GetAllKnowledgeInputProps, - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get<{ - knowledges: BEKnowledge[]; - }>(`/knowledge?brain_id=${brainId}`); - - return response.data.knowledges.map((knowledge) => { - if (knowledge.file_name !== null) { - return { - id: knowledge.id, - brainId: knowledge.brain_id, - fileName: knowledge.file_name, - extension: knowledge.extension, - status: knowledge.status, - integration: knowledge.integration, - integration_link: knowledge.integration_link, - } as UploadedKnowledge; - } else if (knowledge.url !== null) { - return { - id: knowledge.id, - brainId: knowledge.brain_id, - url: knowledge.url, - extension: "URL", - status: knowledge.status, - integration: knowledge.integration, - integration_link: knowledge.integration_link, - } as CrawledKnowledge; - } else { - throw new Error(`Invalid knowledge ${knowledge.id}`); - } - }); -}; - -export type DeleteKnowledgeInputProps = { - brainId: UUID; - knowledgeId: UUID; -}; - -export const deleteKnowledge = async ( - { knowledgeId, brainId }: DeleteKnowledgeInputProps, - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.delete(`/knowledge/${knowledgeId}?brain_id=${brainId}`); -}; - -export const generateSignedUrlKnowledge = async ( - { knowledgeId }: { knowledgeId: UUID }, - axiosInstance: AxiosInstance -): Promise => { - const response = await axiosInstance.get<{ - signedURL: string; - }>(`/knowledge/${knowledgeId}/signed_download_url`); - - return response.data.signedURL; -}; diff --git a/frontend/lib/api/knowledge/useKnowledgeApi.ts b/frontend/lib/api/knowledge/useKnowledgeApi.ts deleted file mode 100644 index 8872377ee..000000000 --- a/frontend/lib/api/knowledge/useKnowledgeApi.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { UUID } from "crypto"; - -import { useAxios } from "@/lib/hooks"; - -import { - deleteKnowledge, - DeleteKnowledgeInputProps, - generateSignedUrlKnowledge, - getAllKnowledge, - GetAllKnowledgeInputProps, -} from "./knowledge"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useKnowledgeApi = () => { - const { axiosInstance } = useAxios(); - - return { - getAllKnowledge: async (props: GetAllKnowledgeInputProps) => - getAllKnowledge(props, axiosInstance), - deleteKnowledge: async (props: DeleteKnowledgeInputProps) => - deleteKnowledge(props, axiosInstance), - generateSignedUrlKnowledge: async (props: { knowledgeId: UUID }) => - generateSignedUrlKnowledge(props, axiosInstance), - }; -}; diff --git a/frontend/lib/api/notification/notification.ts b/frontend/lib/api/notification/notification.ts deleted file mode 100644 index a7ab1caca..000000000 --- a/frontend/lib/api/notification/notification.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { Notification } from "@/app/chat/[chatId]/types"; - -export const getChatNotifications = async ( - chatId: string, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/notifications/${chatId}`)) - .data; -}; diff --git a/frontend/lib/api/notification/useNotificationApi.ts b/frontend/lib/api/notification/useNotificationApi.ts deleted file mode 100644 index 80a5780fa..000000000 --- a/frontend/lib/api/notification/useNotificationApi.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { getChatNotifications } from "./notification"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useNotificationApi = () => { - const { axiosInstance } = useAxios(); - - return { - getChatNotifications: async (chatId: string) => - await getChatNotifications(chatId, axiosInstance), - }; -}; diff --git a/frontend/lib/api/onboarding/__test__/useOnboarding.test.ts b/frontend/lib/api/onboarding/__test__/useOnboarding.test.ts deleted file mode 100644 index da286276f..000000000 --- a/frontend/lib/api/onboarding/__test__/useOnboarding.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { renderHook } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { Onboarding } from "@/lib/types/Onboarding"; - -import { useOnboardingApi } from "../useOnboardingApi"; - -const axiosGetMock = vi.fn(() => ({})); -const axiosPutMock = vi.fn(() => ({})); - -vi.mock("@/lib/hooks", () => ({ - useAxios: () => ({ - axiosInstance: { - get: axiosGetMock, - put: axiosPutMock, - }, - }), -})); - -describe("useOnboarding", () => { - it("should call getOnboarding with the correct parameters", async () => { - axiosGetMock.mockReturnValue({ data: {} }); - const { - result: { - current: { getOnboarding }, - }, - } = renderHook(() => useOnboardingApi()); - - await getOnboarding(); - - expect(axiosGetMock).toHaveBeenCalledTimes(1); - expect(axiosGetMock).toHaveBeenCalledWith("/onboarding"); - }); - it("should call updateOnboarding with the correct parameters", async () => { - const onboarding: Partial = { - onboarding_a: true, - onboarding_b1: false, - }; - axiosPutMock.mockReturnValue({ data: {} }); - const { - result: { - current: { updateOnboarding }, - }, - } = renderHook(() => useOnboardingApi()); - - await updateOnboarding(onboarding); - - expect(axiosPutMock).toHaveBeenCalledTimes(1); - expect(axiosPutMock).toHaveBeenCalledWith("/onboarding", onboarding); - }); -}); diff --git a/frontend/lib/api/onboarding/config.ts b/frontend/lib/api/onboarding/config.ts deleted file mode 100644 index 597f1fd26..000000000 --- a/frontend/lib/api/onboarding/config.ts +++ /dev/null @@ -1 +0,0 @@ -export const ONBOARDING_DATA_KEY = "onboarding"; diff --git a/frontend/lib/api/onboarding/onboarding.ts b/frontend/lib/api/onboarding/onboarding.ts deleted file mode 100644 index b35613bb6..000000000 --- a/frontend/lib/api/onboarding/onboarding.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { Onboarding } from "@/lib/types/Onboarding"; - -export const getOnboarding = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get("/onboarding")).data; -}; - -export const updateOnboarding = async ( - onboarding: Partial, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.put("/onboarding", onboarding)).data; -}; diff --git a/frontend/lib/api/onboarding/useOnboardingApi.ts b/frontend/lib/api/onboarding/useOnboardingApi.ts deleted file mode 100644 index 838cb8583..000000000 --- a/frontend/lib/api/onboarding/useOnboardingApi.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useAxios } from "@/lib/hooks"; -import { Onboarding } from "@/lib/types/Onboarding"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useOnboardingApi = () => { - const { axiosInstance } = useAxios(); - const getOnboarding = async () => { - return (await axiosInstance.get("/onboarding")).data; - }; - const updateOnboarding = async (onboarding: Partial) => { - return (await axiosInstance.put("/onboarding", onboarding)) - .data; - }; - - return { - getOnboarding, - updateOnboarding, - }; -}; diff --git a/frontend/lib/api/prompt/__tests__/usePromptApi.test.ts b/frontend/lib/api/prompt/__tests__/usePromptApi.test.ts deleted file mode 100644 index 26972a723..000000000 --- a/frontend/lib/api/prompt/__tests__/usePromptApi.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { renderHook } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import { CreatePromptProps, PromptUpdatableProperties } from "../prompt"; -import { usePromptApi } from "../usePromptApi"; - -const axiosPostMock = vi.fn(() => ({})); -const axiosGetMock = vi.fn(() => ({})); -const axiosPutMock = vi.fn(() => ({})); - -vi.mock("@/lib/hooks", () => ({ - useAxios: () => ({ - axiosInstance: { - post: axiosPostMock, - get: axiosGetMock, - put: axiosPutMock, - }, - }), -})); - -describe("usePromptApi", () => { - afterEach(() => { - vi.resetAllMocks(); - }); - - it("should call createPrompt with the correct parameters", async () => { - const prompt: CreatePromptProps = { - title: "Test Prompt", - content: "Test Content", - }; - axiosPostMock.mockReturnValue({ data: {} }); - const { - result: { - current: { createPrompt }, - }, - } = renderHook(() => usePromptApi()); - await createPrompt(prompt); - - expect(axiosPostMock).toHaveBeenCalledTimes(1); - expect(axiosPostMock).toHaveBeenCalledWith("/prompts", prompt); - }); - it("should call getPrompt with the correct parameters", async () => { - const promptId = "test-prompt-id"; - axiosGetMock.mockReturnValue({ data: {} }); - const { - result: { - current: { getPrompt }, - }, - } = renderHook(() => usePromptApi()); - - await getPrompt(promptId); - - expect(axiosGetMock).toHaveBeenCalledTimes(1); - expect(axiosGetMock).toHaveBeenCalledWith(`/prompts/${promptId}`); - }); - it("should call updatePrompt with the correct parameters", async () => { - const promptId = "test-prompt-id"; - const prompt: PromptUpdatableProperties = { - title: "Test Prompt", - content: "Test Content", - }; - axiosPutMock.mockReturnValue({ data: {} }); - const { - result: { - current: { updatePrompt }, - }, - } = renderHook(() => usePromptApi()); - - await updatePrompt(promptId, prompt); - - expect(axiosPutMock).toHaveBeenCalledTimes(1); - expect(axiosPutMock).toHaveBeenCalledWith(`/prompts/${promptId}`, prompt); - }); - it("should call getPublicPrompts with the correct parameters", async () => { - axiosGetMock.mockReturnValue({ data: [] }); - const { - result: { - current: { getPublicPrompts }, - }, - } = renderHook(() => usePromptApi()); - - await getPublicPrompts(); - - expect(axiosGetMock).toHaveBeenCalledTimes(1); - expect(axiosGetMock).toHaveBeenCalledWith("/prompts"); - }); -}); diff --git a/frontend/lib/api/prompt/prompt.ts b/frontend/lib/api/prompt/prompt.ts deleted file mode 100644 index bf1b82a51..000000000 --- a/frontend/lib/api/prompt/prompt.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AxiosInstance } from "axios"; - -import { Prompt } from "@/lib/types/Prompt"; - -export type CreatePromptProps = { - title: string; - content: string; -}; - -export const createPrompt = async ( - prompt: CreatePromptProps, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.post("/prompts", prompt)).data; -}; - -export const getPrompt = async ( - promptId: string, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/prompts/${promptId}`)).data; -}; - -export type PromptUpdatableProperties = { - title: string; - content: string; -}; -export const updatePrompt = async ( - promptId: string, - prompt: PromptUpdatableProperties, - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.put(`/prompts/${promptId}`, prompt)).data; -}; - -export const getPublicPrompts = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get("/prompts")).data; -}; diff --git a/frontend/lib/api/prompt/usePromptApi.ts b/frontend/lib/api/prompt/usePromptApi.ts deleted file mode 100644 index 079146133..000000000 --- a/frontend/lib/api/prompt/usePromptApi.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { - createPrompt, - CreatePromptProps, - getPrompt, - getPublicPrompts, - PromptUpdatableProperties, - updatePrompt, -} from "./prompt"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const usePromptApi = () => { - const { axiosInstance } = useAxios(); - - return { - createPrompt: async (prompt: CreatePromptProps) => - createPrompt(prompt, axiosInstance), - getPrompt: async (promptId: string) => getPrompt(promptId, axiosInstance), - updatePrompt: async (promptId: string, prompt: PromptUpdatableProperties) => - updatePrompt(promptId, prompt, axiosInstance), - getPublicPrompts: async () => await getPublicPrompts(axiosInstance), - }; -}; diff --git a/frontend/lib/api/subscription/subscription.ts b/frontend/lib/api/subscription/subscription.ts deleted file mode 100644 index 8881b751a..000000000 --- a/frontend/lib/api/subscription/subscription.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { BrainRoleType } from "@/app/studio/[brainId]/BrainManagementTabs/components/PeopleTab/BrainUsers/types"; - -export const acceptInvitation = async ( - brainId: UUID, - axiosInstance: AxiosInstance -): Promise<{ message: string }> => { - const acceptedInvitation = ( - await axiosInstance.post<{ message: string }>( - `/brains/${brainId}/subscription/accept` - ) - ).data; - - return acceptedInvitation; -}; - -export const declineInvitation = async ( - brainId: UUID, - axiosInstance: AxiosInstance -): Promise<{ message: string }> => { - const deletedInvitation = ( - await axiosInstance.post<{ message: string }>( - `/brains/${brainId}/subscription/decline` - ) - ).data; - - return deletedInvitation; -}; - -export type InvitationBrain = { - name: string; - role: BrainRoleType; -}; - -//TODO: rename rights to role in Backend and use InvitationBrain instead of BackendInvitationBrain -type BackendInvitationBrain = Omit & { - rights: BrainRoleType; -}; - -export const getInvitation = async ( - brainId: UUID, - axiosInstance: AxiosInstance -): Promise => { - const invitation = ( - await axiosInstance.get( - `/brains/${brainId}/subscription` - ) - ).data; - - return { - name: invitation.name, - role: invitation.rights, - }; -}; -type ApiBrainSecrets = Record; - -export const subscribeToBrain = async ( - brainId: UUID, - axiosInstance: AxiosInstance, - secrets?: ApiBrainSecrets -): Promise<{ message: string }> => { - const subscribedToBrain = ( - await axiosInstance.post<{ message: string }>( - `/brains/${brainId}/subscribe`, - secrets - ) - ).data; - - return subscribedToBrain; -}; - -export const unsubscribeFromBrain = async ( - brainId: UUID, - axiosInstance: AxiosInstance -): Promise<{ message: string }> => - ( - await axiosInstance.post<{ message: string }>( - `/brains/${brainId}/unsubscribe` - ) - ).data; diff --git a/frontend/lib/api/subscription/useSubscriptionApi.ts b/frontend/lib/api/subscription/useSubscriptionApi.ts deleted file mode 100644 index 4ee01e64e..000000000 --- a/frontend/lib/api/subscription/useSubscriptionApi.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { UUID } from "crypto"; - -import { useAxios } from "@/lib/hooks"; - -import { - acceptInvitation, - declineInvitation, - getInvitation, - subscribeToBrain, - unsubscribeFromBrain, -} from "./subscription"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useSubscriptionApi = () => { - const { axiosInstance } = useAxios(); - - return { - acceptInvitation: async (brainId: UUID) => - acceptInvitation(brainId, axiosInstance), - declineInvitation: async (brainId: UUID) => - declineInvitation(brainId, axiosInstance), - getInvitation: async (brainId: UUID) => - getInvitation(brainId, axiosInstance), - subscribeToBrain: async (brainId: UUID, secrets?: Record) => - subscribeToBrain(brainId, axiosInstance, secrets), - unsubscribeFromBrain: async (brainId: UUID) => - unsubscribeFromBrain(brainId, axiosInstance), - }; -}; diff --git a/frontend/lib/api/sync/sync.ts b/frontend/lib/api/sync/sync.ts deleted file mode 100644 index a85829d01..000000000 --- a/frontend/lib/api/sync/sync.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { - ActiveSync, - OpenedConnection, - Sync, - SyncElement, - SyncElements, -} from "./types"; - -const createFilesSettings = (files: SyncElement[]) => - files.filter((file) => !file.is_folder).map((file) => file.id); - -const createFoldersSettings = (files: SyncElement[]) => - files.filter((file) => file.is_folder).map((file) => file.id); - -export const syncGoogleDrive = async ( - name: string, - axiosInstance: AxiosInstance -): Promise<{ authorization_url: string }> => { - return ( - await axiosInstance.post<{ authorization_url: string }>( - `/sync/google/authorize?name=${name}` - ) - ).data; -}; - -export const syncSharepoint = async ( - name: string, - axiosInstance: AxiosInstance -): Promise<{ authorization_url: string }> => { - return ( - await axiosInstance.post<{ authorization_url: string }>( - `/sync/azure/authorize?name=${name}` - ) - ).data; -}; - -export const syncDropbox = async ( - name: string, - axiosInstance: AxiosInstance -): Promise<{ authorization_url: string }> => { - return ( - await axiosInstance.post<{ authorization_url: string }>( - `/sync/dropbox/authorize?name=${name}` - ) - ).data; -}; - -export const syncNotion = async ( - name: string, - axiosInstance: AxiosInstance -): Promise<{ authorization_url: string }> => { - return ( - await axiosInstance.post<{ authorization_url: string }>( - `/sync/notion/authorize?name=${name}` - ) - ).data; -}; - -export const getUserSyncs = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get("/sync")).data; -}; - -export const getSyncFiles = async ( - axiosInstance: AxiosInstance, - userSyncId: number, - folderId?: string -): Promise => { - const url = folderId - ? `/sync/${userSyncId}/files?user_sync_id=${userSyncId}&folder_id=${folderId}` - : `/sync/${userSyncId}/files?user_sync_id=${userSyncId}`; - - return (await axiosInstance.get(url)).data; -}; - -export const syncFiles = async ( - axiosInstance: AxiosInstance, - openedConnection: OpenedConnection, - brainId: UUID -): Promise => { - return ( - await axiosInstance.post(`/sync/active`, { - name: openedConnection.name, - syncs_user_id: openedConnection.user_sync_id, - settings: { - files: createFilesSettings(openedConnection.selectedFiles.files), - folders: createFoldersSettings(openedConnection.selectedFiles.files), - }, - brain_id: brainId, - }) - ).data; -}; - -export const updateActiveSync = async ( - axiosInstance: AxiosInstance, - openedConnection: OpenedConnection -): Promise => { - return ( - await axiosInstance.put(`/sync/active/${openedConnection.id}`, { - name: openedConnection.name, - settings: { - files: createFilesSettings(openedConnection.selectedFiles.files), - folders: createFoldersSettings(openedConnection.selectedFiles.files), - }, - last_synced: openedConnection.last_synced, - }) - ).data; -}; - -export const deleteActiveSync = async ( - axiosInstance: AxiosInstance, - syncId: number -): Promise => { - await axiosInstance.delete(`/sync/active/${syncId}`); -}; - -export const getActiveSyncs = async ( - axiosInstance: AxiosInstance -): Promise => { - return (await axiosInstance.get(`/sync/active`)).data; -}; - -export const deleteUserSync = async ( - axiosInstance: AxiosInstance, - syncId: number -): Promise => { - return (await axiosInstance.delete(`/sync/${syncId}`)).data; -}; diff --git a/frontend/lib/api/sync/types.ts b/frontend/lib/api/sync/types.ts deleted file mode 100644 index 75fae2764..000000000 --- a/frontend/lib/api/sync/types.ts +++ /dev/null @@ -1,64 +0,0 @@ -export type Provider = "Google" | "Azure" | "DropBox" | "Notion" | "GitHub"; - -export type Integration = - | "Google Drive" - | "Share Point" - | "Dropbox" - | "Notion" - | "GitHub"; - -export type SyncStatus = "SYNCING" | "SYNCED" | "ERROR" | "REMOVED"; - -export interface SyncElement { - name?: string; - id: string; - is_folder: boolean; - icon?: string; -} - -export interface SyncElements { - files: SyncElement[]; -} - -interface Credentials { - token: string; -} - -export interface Sync { - name: string; - provider: Provider; - id: number; - credentials: Credentials; - email: string; - status: SyncStatus; -} - -export interface SyncSettings { - folders?: string[]; - files?: string[]; -} - -export interface ActiveSync { - id: number; - name: string; - syncs_user_id: number; - user_id: string; - settings: SyncSettings; - last_synced: string; - sync_interval_minutes: number; - brain_id: string; - syncs_user: { - provider: Provider; - }; -} - -export interface OpenedConnection { - user_sync_id: number; - id: number | undefined; - provider: Provider; - submitted: boolean; - selectedFiles: SyncElements; - name: string; - last_synced: string; - cleaned?: boolean; -} diff --git a/frontend/lib/api/sync/useSync.ts b/frontend/lib/api/sync/useSync.ts deleted file mode 100644 index 94903fbf1..000000000 --- a/frontend/lib/api/sync/useSync.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { UUID } from "crypto"; - -import { useAxios } from "@/lib/hooks"; - -import { - deleteActiveSync, - deleteUserSync, - getActiveSyncs, - getSyncFiles, - getUserSyncs, - syncDropbox, - syncFiles, - syncGoogleDrive, - syncNotion, - syncSharepoint, - updateActiveSync, -} from "./sync"; -import { Integration, OpenedConnection, Provider } from "./types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useSync = () => { - const { axiosInstance } = useAxios(); - - const providerIconUrls: Record = { - Google: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/gdrive_8316d080fd.png", - Azure: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/sharepoint_8c41cfdb09.png", - DropBox: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/dropbox_dce4f3d753.png", - Notion: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/Notion_app_logo_004168672c.png", - GitHub: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/dropbox_dce4f3d753.png", - }; - - const integrationIconUrls: Record = { - "Google Drive": - "https://quivr-cms.s3.eu-west-3.amazonaws.com/gdrive_8316d080fd.png", - "Share Point": - "https://quivr-cms.s3.eu-west-3.amazonaws.com/sharepoint_8c41cfdb09.png", - Dropbox: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/dropbox_dce4f3d753.png", - Notion: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/Notion_app_logo_004168672c.png", - GitHub: - "https://quivr-cms.s3.eu-west-3.amazonaws.com/dropbox_dce4f3d753.png", - }; - - const getActiveSyncsForBrain = async (brainId: string) => { - const activeSyncs = await getActiveSyncs(axiosInstance); - - return activeSyncs.filter((sync) => sync.brain_id === brainId); - }; - - return { - syncGoogleDrive: async (name: string) => - syncGoogleDrive(name, axiosInstance), - syncSharepoint: async (name: string) => syncSharepoint(name, axiosInstance), - syncDropbox: async (name: string) => syncDropbox(name, axiosInstance), - syncNotion: async (name: string) => syncNotion(name, axiosInstance), - getUserSyncs: async () => getUserSyncs(axiosInstance), - getSyncFiles: async (userSyncId: number, folderId?: string) => - getSyncFiles(axiosInstance, userSyncId, folderId), - integrationIconUrls, - providerIconUrls, - syncFiles: async (openedConnection: OpenedConnection, brainId: UUID) => - syncFiles(axiosInstance, openedConnection, brainId), - getActiveSyncs: async () => getActiveSyncs(axiosInstance), - getActiveSyncsForBrain, - deleteUserSync: async (syncId: number) => - deleteUserSync(axiosInstance, syncId), - deleteActiveSync: async (syncId: number) => - deleteActiveSync(axiosInstance, syncId), - updateActiveSync: async (openedConnection: OpenedConnection) => - updateActiveSync(axiosInstance, openedConnection), - }; -}; diff --git a/frontend/lib/api/tests/getNock.ts b/frontend/lib/api/tests/getNock.ts deleted file mode 100644 index 87f91104e..000000000 --- a/frontend/lib/api/tests/getNock.ts +++ /dev/null @@ -1,18 +0,0 @@ -import nock from "nock"; -import { vi } from "vitest"; - -import { DEFAULT_BACKEND_URL } from "@/lib/config/CONSTANTS"; - -vi.mock("@/lib/context/SupabaseProvider", () => ({ - useSupabase: () => ({}), -})); - -export const getNock = (url?: string): nock.Scope => { - return nock( - url ?? `${process.env.NEXT_PUBLIC_BACKEND_URL ?? DEFAULT_BACKEND_URL}` - ).defaultReplyHeaders({ - "access-control-allow-origin": "*", - "access-control-allow-credentials": "true", - "access-control-allow-headers": "Authorization", - }); -}; diff --git a/frontend/lib/api/upload/upload.ts b/frontend/lib/api/upload/upload.ts deleted file mode 100644 index a8d2de7c7..000000000 --- a/frontend/lib/api/upload/upload.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { ToastData } from "@/lib/components/ui/Toast/domain/types"; - -export type UploadResponse = { - data: { type: ToastData["variant"]; message: ToastData["text"] }; -}; - -export type UploadInputProps = { - brainId: UUID; - formData: FormData; - chat_id?: UUID; - bulk_id: UUID; -}; - -export const uploadFile = async ( - props: UploadInputProps, - axiosInstance: AxiosInstance -): Promise => { - let uploadUrl = `/upload?bulk_id=${props.bulk_id}&brain_id=${props.brainId}`; - if (props.chat_id !== undefined) { - uploadUrl = uploadUrl.concat(`&chat_id=${props.chat_id}`); - } - - return axiosInstance.post(uploadUrl, props.formData); -}; diff --git a/frontend/lib/api/upload/useUploadApi.ts b/frontend/lib/api/upload/useUploadApi.ts deleted file mode 100644 index 353944f84..000000000 --- a/frontend/lib/api/upload/useUploadApi.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { uploadFile, UploadInputProps } from "./upload"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useUploadApi = () => { - const { axiosInstance } = useAxios(); - - return { - uploadFile: async (props: UploadInputProps) => - uploadFile(props, axiosInstance), - }; -}; diff --git a/frontend/lib/api/user/config.ts b/frontend/lib/api/user/config.ts deleted file mode 100644 index b6df18e54..000000000 --- a/frontend/lib/api/user/config.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const USER_IDENTITY_DATA_KEY = "user-identity-data"; -export const USER_DATA_KEY = "user-data"; diff --git a/frontend/lib/api/user/useUserApi.ts b/frontend/lib/api/user/useUserApi.ts deleted file mode 100644 index 690272339..000000000 --- a/frontend/lib/api/user/useUserApi.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useAxios } from "@/lib/hooks"; - -import { - deleteUserData, - getUser, - getUserCredits, - getUserIdentity, - updateUserIdentity, - UserIdentityUpdatableProperties, -} from "./user"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useUserApi = () => { - const { axiosInstance } = useAxios(); - - return { - updateUserIdentity: async ( - userIdentityUpdatableProperties: UserIdentityUpdatableProperties - ) => updateUserIdentity(userIdentityUpdatableProperties, axiosInstance), - getUserIdentity: async () => getUserIdentity(axiosInstance), - getUser: async () => getUser(axiosInstance), - deleteUserData: async () => deleteUserData(axiosInstance), - getUserCredits: async () => getUserCredits(axiosInstance), - }; -}; diff --git a/frontend/lib/api/user/user.ts b/frontend/lib/api/user/user.ts deleted file mode 100644 index 3d66a807d..000000000 --- a/frontend/lib/api/user/user.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { AxiosInstance } from "axios"; -import { UUID } from "crypto"; - -import { UserStats } from "@/lib/types/User"; - -export enum CompanySize { - One = "1-10", - Two = "10-25", - Three = "25-50", - Four = "50-100", - Five = "100-250", - Six = "250-500", - Seven = "500-1000", - Eight = "1000-5000", - Nine = "+5000", -} - -export enum UsagePurpose { - Business = "Business", - NGO = "NGO", - Personal = "Personal", - Student = "Student", - Teacher = "Teacher", -} - -export type UserIdentityUpdatableProperties = { - username: string; - company?: string; - onboarded: boolean; - company_size?: CompanySize; - usage_purpose?: UsagePurpose; -}; - -export type UserIdentity = { - id: UUID; - onboarded: boolean; - username: string; -}; - -export const updateUserIdentity = async ( - userUpdatableProperties: UserIdentityUpdatableProperties, - axiosInstance: AxiosInstance -): Promise => - axiosInstance.put(`/user/identity`, userUpdatableProperties); - -export const getUserIdentity = async ( - axiosInstance: AxiosInstance -): Promise => { - const { data } = await axiosInstance.get(`/user/identity`); - - return data; -}; - -export const getUser = async ( - axiosInstance: AxiosInstance -): Promise => (await axiosInstance.get("/user")).data; - -export const deleteUserData = async ( - axiosInstance: AxiosInstance -): Promise => { - await axiosInstance.delete(`/user_data`); -}; - -export const getUserCredits = async ( - axiosInstance: AxiosInstance -): Promise => (await axiosInstance.get("/user/credits")).data; diff --git a/frontend/lib/assets/QuivrLogo.tsx b/frontend/lib/assets/QuivrLogo.tsx deleted file mode 100644 index 7c12738f0..000000000 --- a/frontend/lib/assets/QuivrLogo.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Image from "next/image"; -import { useEffect, useState } from "react"; - -interface QuivrLogoProps { - size: number; - color?: "white" | "black" | "primary" | "accent"; -} - -export const QuivrLogo = ({ - size, - color = "white", -}: QuivrLogoProps): JSX.Element => { - const [src, setSrc] = useState("/logo-white.svg"); - - useEffect(() => { - if (color === "primary") { - setSrc("/logo-primary.svg"); - } else if (color === "accent") { - setSrc("/logo-accent.svg"); - } else if (color === "black") { - setSrc("/logo-black.svg"); - } else { - setSrc("/logo-white.svg"); - } - }, [color]); - - return Quivr Logo; -}; diff --git a/frontend/lib/components/AddBrainModal/AddBrainModal.module.scss b/frontend/lib/components/AddBrainModal/AddBrainModal.module.scss deleted file mode 100644 index 6655484a0..000000000 --- a/frontend/lib/components/AddBrainModal/AddBrainModal.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.add_brain_modal_container { - display: flex; - padding-top: Spacings.$spacing06; - flex-direction: column; - width: 100%; - flex-grow: 1; - overflow: hidden; - gap: Spacings.$spacing05; - - .stepper_container { - width: 100%; - } - - .content_wrapper { - flex-grow: 1; - overflow: auto; - } -} diff --git a/frontend/lib/components/AddBrainModal/AddBrainModal.tsx b/frontend/lib/components/AddBrainModal/AddBrainModal.tsx deleted file mode 100644 index bc4517d55..000000000 --- a/frontend/lib/components/AddBrainModal/AddBrainModal.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect } from "react"; -import { FormProvider, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; - -import styles from "./AddBrainModal.module.scss"; -import { useBrainCreationContext } from "./brainCreation-provider"; -import { BrainMainInfosStep } from "./components/BrainMainInfosStep/BrainMainInfosStep"; -import { BrainRecapStep } from "./components/BrainRecapStep/BrainRecapStep"; -import { FeedBrainStep } from "./components/FeedBrainStep/FeedBrainStep"; -import { Stepper } from "./components/Stepper/Stepper"; -import { useBrainCreationSteps } from "./hooks/useBrainCreationSteps"; -import { CreateBrainProps } from "./types/types"; - -export const AddBrainModal = (): JSX.Element => { - const { t } = useTranslation(["translation", "brain", "config"]); - const { currentStep, steps } = useBrainCreationSteps(); - const { setCurrentBrainId } = useBrainContext(); - const { setSnippetColor, setSnippetEmoji } = useBrainCreationContext(); - const { - isBrainCreationModalOpened, - setIsBrainCreationModalOpened, - setCurrentSelectedBrain, - setCreating, - } = useBrainCreationContext(); - const { setCurrentSyncId, setOpenedConnections } = - useFromConnectionsContext(); - const { removeAllKnowledgeToFeed } = useKnowledgeToFeedContext(); - - const defaultValues: CreateBrainProps = { - ...addBrainDefaultValues, - setDefault: true, - brainCreationStep: "FIRST_STEP", - }; - - const methods = useForm({ - defaultValues, - }); - - useEffect(() => { - setCurrentSelectedBrain(undefined); - setCurrentSyncId(undefined); - setCreating(false); - setOpenedConnections([]); - methods.reset(defaultValues); - removeAllKnowledgeToFeed(); - if (isBrainCreationModalOpened) { - setCurrentBrainId(null); - setSnippetColor("#d0c6f2"); - setSnippetEmoji("🧠"); - } - }, [isBrainCreationModalOpened]); - - return ( - - } - > -
-
- -
-
- - - -
-
-
-
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx b/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx deleted file mode 100644 index c5c761bba..000000000 --- a/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { createContext, useContext, useState } from "react"; - -import { IntegrationBrains } from "@/lib/api/brain/types"; - -import { StepValue } from "./types/types"; - -interface BrainCreationContextProps { - isBrainCreationModalOpened: boolean; - setIsBrainCreationModalOpened: React.Dispatch>; - creating: boolean; - setCreating: React.Dispatch>; - currentSelectedBrain: IntegrationBrains | undefined; - setCurrentSelectedBrain: React.Dispatch< - React.SetStateAction - >; - currentStep: StepValue; - setCurrentStep: React.Dispatch>; - snippetColor: string; - setSnippetColor: React.Dispatch>; - snippetEmoji: string; - setSnippetEmoji: React.Dispatch>; -} - -export const BrainCreationContext = createContext< - BrainCreationContextProps | undefined ->(undefined); - -export const BrainCreationProvider = ({ - children, -}: { - children: React.ReactNode; -}): JSX.Element => { - const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] = - useState(false); - const [currentSelectedBrain, setCurrentSelectedBrain] = - useState(); - const [creating, setCreating] = useState(false); - const [currentStep, setCurrentStep] = useState("FIRST_STEP"); - const [snippetColor, setSnippetColor] = useState("#d0c6f2"); - const [snippetEmoji, setSnippetEmoji] = useState("🧠"); - - return ( - - {children} - - ); -}; - -export const useBrainCreationContext = (): BrainCreationContextProps => { - const context = useContext(BrainCreationContext); - if (!context) { - throw new Error( - "useBrainCreationContext must be used within a BrainCreationProvider" - ); - } - - return context; -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.module.scss b/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.module.scss deleted file mode 100644 index 077d1e17f..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.module.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.brain_main_infos_container { - position: relative; - height: 100%; - - .brain_main_infos_wrapper { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - gap: Spacings.$spacing05; - position: relative; - - .title { - @include Typography.H3; - } - - .inputs_wrapper { - display: flex; - flex-direction: column; - overflow: auto; - position: relative; - - .first_line_wrapper { - display: flex; - align-items: bottom; - gap: Spacings.$spacing08; - justify-content: space-between; - padding-top: Spacings.$spacing05; - - .name_field { - width: 300px; - align-self: flex-start; - } - - @media (max-width: ScreenSizes.$small) { - .name_field { - width: 100%; - } - } - - .brain_snippet_wrapper { - display: flex; - flex-direction: column; - align-items: center; - gap: Spacings.$spacing03; - align-self: top; - position: relative; - - @media screen and (max-width: ScreenSizes.$small) { - position: relative; - - .edit_snippet { - width: 100%; - } - } - - .brain_snippet { - width: 64px; - height: 64px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - font-size: Typography.$very_large; - } - - .button_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - font-size: Typography.$small; - gap: Spacings.$spacing03; - } - } - } - } - - .buttons_wrapper { - display: flex; - justify-content: flex-end; - } - } - - .edit_snippet { - position: absolute; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - top: 0; - z-index: ZIndexes.$modal; - } -} diff --git a/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.tsx b/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.tsx deleted file mode 100644 index 9466d23db..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainMainInfosStep/BrainMainInfosStep.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useState } from "react"; -import { Controller, useFormContext } from "react-hook-form"; - -import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types"; -import { BrainSnippet } from "@/lib/components/BrainSnippet/BrainSnippet"; -import { FieldHeader } from "@/lib/components/ui/FieldHeader/FieldHeader"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { TextAreaInput } from "@/lib/components/ui/TextAreaInput/TextAreaInput"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; - -import styles from "./BrainMainInfosStep.module.scss"; - -import { useBrainCreationContext } from "../../brainCreation-provider"; -import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; - -export const BrainMainInfosStep = (): JSX.Element => { - const [editSnippet, setEditSnippet] = useState(false); - const { currentStepIndex, goToNextStep } = useBrainCreationSteps(); - const { snippetColor, setSnippetColor, snippetEmoji, setSnippetEmoji } = - useBrainCreationContext(); - - const { watch } = useFormContext(); - const name = watch("name"); - const description = watch("description"); - - const isDisabled = !name || !description; - - const next = (): void => { - goToNextStep(); - }; - - if (currentStepIndex !== 0) { - return <>; - } - - return ( -
-
-
- Define brain identity -
-
- - ( - - )} - /> -
-
-
{ - if (!editSnippet) { - setEditSnippet(true); - } - }} - > - {snippetEmoji} -
- setEditSnippet(true)} - small={true} - /> -
-
-
- - ( - - )} - /> -
-
-
- next()} - iconName="chevronRight" - disabled={isDisabled} - important={true} - /> -
-
- {editSnippet && ( -
- { - setSnippetColor(color); - setSnippetEmoji(emoji); - }} - /> -
- )} -
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.module.scss b/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.module.scss deleted file mode 100644 index e48548da8..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_recap_card_wrapper { - display: flex; - padding: Spacings.$spacing05; - justify-content: center; - box-shadow: BoxShadow.$small; - border-radius: Radius.$normal; - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .number_label { - @include Typography.Big; - color: var(--primary-0); - } - - .type { - @include Typography.H1; - } -} diff --git a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.tsx b/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.tsx deleted file mode 100644 index b6e7d76dd..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapCard/BrainRecapCard.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import styles from "./BrainRecapCard.module.scss"; - -interface BrainRecapCardProps { - label: string; - number: number; -} - -export const BrainRecapCard = ({ - label, - number, -}: BrainRecapCardProps): JSX.Element => { - return ( -
- {number.toString()} - - {label} - {number > 1 ? "s" : ""} - -
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.module.scss b/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.module.scss deleted file mode 100644 index 349323a54..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.module.scss +++ /dev/null @@ -1,76 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_recap_wrapper { - display: flex; - justify-content: space-between; - flex-direction: column; - height: 100%; - gap: Spacings.$spacing05; - overflow: hidden; - - .content_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - overflow: auto; - - .title { - @include Typography.H3; - } - - .subtitle { - font-size: Typography.$small; - font-weight: 500; - } - - .warning_message { - font-size: Typography.$small; - } - - .brain_info_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - - .name_field { - width: 300px; - } - - @media screen and (max-width: ScreenSizes.$small) { - .name_field { - min-width: 100%; - max-width: 100%; - } - } - } - - .cards_wrapper { - display: flex; - flex-wrap: wrap; - padding: Spacings.$spacing01; - justify-content: space-between; - gap: Spacings.$spacing05; - - > * { - min-width: 120px; - max-width: 200px; - flex: 1; - } - - @media screen and (max-width: ScreenSizes.$small) { - > * { - min-width: 100%; - max-width: 100%; - flex: 1; - } - } - } - } - - .buttons_wrapper { - display: flex; - justify-content: space-between; - } -} diff --git a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.tsx b/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.tsx deleted file mode 100644 index 93de8f3be..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainRecapStep/BrainRecapStep.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { Controller } from "react-hook-form"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { TextAreaInput } from "@/lib/components/ui/TextAreaInput/TextAreaInput"; -import { TextInput } from "@/lib/components/ui/TextInput/TextInput"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useOnboardingContext } from "@/lib/context/OnboardingProvider/hooks/useOnboardingContext"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import { BrainRecapCard } from "./BrainRecapCard/BrainRecapCard"; -import styles from "./BrainRecapStep.module.scss"; - -import { useBrainCreationContext } from "../../brainCreation-provider"; -import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; -import { useBrainCreationApi } from "../FeedBrainStep/hooks/useBrainCreationApi"; - -export const BrainRecapStep = (): JSX.Element => { - const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps(); - const { creating, setCreating } = useBrainCreationContext(); - const { knowledgeToFeed } = useKnowledgeToFeedContext(); - const { createBrain } = useBrainCreationApi(); - const { updateUserIdentity } = useUserApi(); - const { userIdentityData } = useUserData(); - const { openedConnections } = useFromConnectionsContext(); - const { setIsBrainCreated } = useOnboardingContext(); - - const feed = async (): Promise => { - if (!userIdentityData?.onboarded) { - await updateUserIdentity({ - ...userIdentityData, - username: userIdentityData?.username ?? "", - onboarded: true, - }); - } - setCreating(true); - createBrain(); - }; - - const previous = (): void => { - goToPreviousStep(); - }; - - if (currentStepIndex !== 2) { - return <>; - } - - return ( -
-
- - - Depending on the number of knowledge, the upload can take - few minutes. - - - Brain Recap -
-
- ( - - )} - /> -
-
- ( - - )} - /> -
-
- Knowledge From -
- connection.selectedFiles.files.length - ).length - } - /> - knowledge.source === "crawl" - ).length - } - /> - knowledge.source === "upload" - ).length - } - /> -
-
-
- - { - await feed(); - setIsBrainCreated(true); - }} - isLoading={creating} - important={true} - /> -
-
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.module.scss b/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.module.scss deleted file mode 100644 index 2ebfe6808..000000000 --- a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.module.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_knowledge_wrapper { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - - .tutorial { - padding-bottom: Spacings.$spacing05; - } - - .feed_brain { - display: flex; - flex-direction: column; - overflow: auto; - height: 100%; - - .title { - @include Typography.H3; - } - } - - .message_info_box_wrapper { - align-self: center; - display: flex; - - .message_content { - display: flex; - gap: Spacings.$spacing03; - flex-wrap: wrap; - align-items: center; - align-self: center; - } - } - - .buttons_wrapper { - display: flex; - justify-content: space-between; - } -} diff --git a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.tsx b/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.tsx deleted file mode 100644 index 887845e89..000000000 --- a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/FeedBrainStep.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useEffect, useState } from "react"; - -import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components"; -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { OpenedConnection } from "@/lib/api/sync/types"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { createHandleGetButtonProps } from "@/lib/helpers/handleConnectionButtons"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import styles from "./FeedBrainStep.module.scss"; - -import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; - -export const FeedBrainStep = (): JSX.Element => { - const { currentStepIndex, goToPreviousStep, goToNextStep } = - useBrainCreationSteps(); - const { userIdentityData } = useUserData(); - const { - currentSyncId, - setCurrentSyncId, - openedConnections, - setOpenedConnections, - } = useFromConnectionsContext(); - const [currentConnection, setCurrentConnection] = useState< - OpenedConnection | undefined - >(undefined); - const { knowledgeToFeed } = useKnowledgeToFeedContext(); - - useEffect(() => { - setCurrentConnection( - openedConnections.find( - (connection) => connection.user_sync_id === currentSyncId - ) - ); - }, [currentSyncId]); - - const getButtonProps = createHandleGetButtonProps( - currentConnection, - openedConnections, - setOpenedConnections, - currentSyncId, - setCurrentSyncId - ); - - const renderFeedBrain = () => ( - <> -
- Feed your brain - -
- - ); - - const renderButtons = () => { - const buttonProps = getButtonProps(); - - return ( -
- {currentSyncId ? ( - setCurrentSyncId(undefined)} - /> - ) : ( - - )} - {currentSyncId ? ( - - ) : ( - - )} -
- ); - }; - - if (currentStepIndex !== 1) { - return <>; - } - - return ( -
- {renderFeedBrain()} - {renderButtons()} -
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/hooks/useBrainCreationApi.ts b/frontend/lib/components/AddBrainModal/components/FeedBrainStep/hooks/useBrainCreationApi.ts deleted file mode 100644 index 92b5a6e76..000000000 --- a/frontend/lib/components/AddBrainModal/components/FeedBrainStep/hooks/useBrainCreationApi.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { AxiosError } from "axios"; -import { UUID } from "crypto"; -import { useState } from "react"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { v4 as uuidv4 } from "uuid"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { PUBLIC_BRAINS_KEY } from "@/lib/api/brain/config"; -import { IntegrationSettings } from "@/lib/api/brain/types"; -import { useSync } from "@/lib/api/sync/useSync"; -import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types"; -import { useKnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useToast } from "@/lib/hooks"; -import { useKnowledgeToFeedFilesAndUrls } from "@/lib/hooks/useKnowledgeToFeed"; - -import { useBrainCreationContext } from "../../../brainCreation-provider"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainCreationApi = () => { - const queryClient = useQueryClient(); - const { publish } = useToast(); - const { t } = useTranslation(["brain", "config"]); - const { files, urls } = useKnowledgeToFeedFilesAndUrls(); - const { getValues, reset } = useFormContext(); - const { setKnowledgeToFeed } = useKnowledgeToFeedContext(); - const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext(); - const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput(); - const { - setIsBrainCreationModalOpened, - setCreating, - currentSelectedBrain, - snippetColor, - snippetEmoji, - } = useBrainCreationContext(); - const { setOpenedConnections } = useFromConnectionsContext(); - const [fields, setFields] = useState< - { name: string; type: string; value: string }[] - >([]); - const { syncFiles } = useSync(); - const { openedConnections } = useFromConnectionsContext(); - - const handleFeedBrain = async (brainId: UUID): Promise => { - const crawlBulkId: UUID = uuidv4().toString() as UUID; - const uploadBulkId: UUID = uuidv4().toString() as UUID; - - const uploadPromises = files.map((file) => - uploadFileHandler(file, brainId, uploadBulkId) - ); - - const crawlPromises = urls.map((url) => - crawlWebsiteHandler(url, brainId, crawlBulkId) - ); - - await Promise.all([...uploadPromises, ...crawlPromises]); - await Promise.all( - openedConnections - .filter((connection) => connection.selectedFiles.files.length) - .map(async (openedConnection) => { - await syncFiles(openedConnection, brainId); - }) - ); - setKnowledgeToFeed([]); - }; - - const createBrain = async (): Promise => { - const { name, description } = getValues(); - let integrationSettings: IntegrationSettings | undefined = undefined; - - if (currentSelectedBrain) { - integrationSettings = { - integration_id: currentSelectedBrain.id, - settings: fields.reduce((acc, field) => { - acc[field.name] = field.value; - - return acc; - }, {} as { [key: string]: string }), - }; - } - - const createdBrainId = await createBrainApi({ - brain_type: currentSelectedBrain ? "integration" : "doc", - name, - description, - integration: integrationSettings, - snippet_color: snippetColor, - snippet_emoji: snippetEmoji, - }); - - if (createdBrainId === undefined) { - publish({ - variant: "danger", - text: t("errorCreatingBrain", { ns: "brain" }), - }); - - return; - } - - void handleFeedBrain(createdBrainId); - - setCurrentBrainId(createdBrainId); - setIsBrainCreationModalOpened(false); - setCreating(false); - setOpenedConnections([]); - reset(); - - void queryClient.invalidateQueries({ - queryKey: [PUBLIC_BRAINS_KEY], - }); - }; - - const { mutate, isPending: isBrainCreationPending } = useMutation({ - mutationFn: createBrain, - onSuccess: () => { - publish({ - variant: "success", - text: t("brainCreated", { ns: "brain" }), - }); - }, - onError: (error: AxiosError) => { - if (error.response && error.response.status === 429) { - publish({ - variant: "danger", - text: "You have reached your maximum amount of brains. Upgrade your plan to create more.", - }); - } else { - publish({ - variant: "danger", - text: t("errorCreatingBrain", { ns: "brain" }), - }); - } - }, - }); - - return { - createBrain: mutate, - isBrainCreationPending, - fields, - setFields, - }; -}; diff --git a/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.module.scss b/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.module.scss deleted file mode 100644 index f1563704e..000000000 --- a/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.module.scss +++ /dev/null @@ -1,118 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.stepper_wrapper { - display: flex; - width: 100%; - justify-content: space-between; - overflow: visible; - - .step { - display: flex; - flex-direction: column; - border-radius: Radius.$circle; - position: relative; - - .circle { - width: 1.75rem; - height: 1.75rem; - background-color: var(--primary-0); - border-radius: Radius.$circle; - display: flex; - justify-content: center; - align-items: center; - - .inside_circle { - width: 100%; - height: 100%; - border-radius: Radius.$circle; - display: flex; - justify-content: center; - align-items: center; - } - } - - .step_info { - margin-top: Spacings.$spacing03; - display: flex; - flex-direction: column; - font-size: Typography.$tiny; - width: 1.75rem; - align-items: center; - - .step_index { - white-space: nowrap; - color: var(--text-1); - } - } - - &.done_step { - .circle { - background-color: var(--success); - } - - .step_info { - .step_status { - color: var(--success); - } - } - } - - &.current_step { - .circle { - background-color: var(--background-0); - border: 1px solid var(--primary-0); - } - - .inside_circle { - background-color: var(--primary-0); - width: 70%; - height: 70%; - } - - .step_info { - .step_status { - color: var(--primary-0); - } - } - } - - &.pending_step { - .circle { - background-color: var(--primary-1); - } - - .step_info { - .step_status { - color: var(--text-1); - } - } - } - - &:first-child { - .step_info { - align-items: start; - } - } - - &:last-child { - .step_info { - align-items: end; - } - } - } - - .bar { - flex-grow: 1; - height: 4px; - border-radius: Radius.$big; - background-color: var(--primary-1); - margin: 0 8px; - margin-top: Spacings.$spacing04; - - &.done { - background-color: var(--success); - } - } -} diff --git a/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.tsx b/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.tsx deleted file mode 100644 index 1ae3c8717..000000000 --- a/frontend/lib/components/AddBrainModal/components/Stepper/Stepper.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; - -import styles from "./Stepper.module.scss"; - -import { StepValue } from "../../types/types"; - -interface StepperProps { - currentStep: StepValue; - steps: { value: string; label: string }[]; -} - -export const Stepper = ({ currentStep, steps }: StepperProps): JSX.Element => { - const currentStepIndex = steps.findIndex( - (step) => step.value === currentStep - ); - - return ( -
- {steps.map((step, index) => ( - -
-
-
- {index < currentStepIndex && ( - - )} -
-
-
- STEP {index + 1} - - {index === currentStepIndex - ? "Progress" - : index < currentStepIndex - ? "Completed" - : "Pending"} - -
-
- {index < steps.length - 1 && ( -
- )} -
- ))} -
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/hooks/useBrainCreationSteps.ts b/frontend/lib/components/AddBrainModal/hooks/useBrainCreationSteps.ts deleted file mode 100644 index 66075576d..000000000 --- a/frontend/lib/components/AddBrainModal/hooks/useBrainCreationSteps.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useEffect } from "react"; -import { useTranslation } from "react-i18next"; - -import { Step } from "@/lib/types/Modal"; - -import { useBrainCreationContext } from "../brainCreation-provider"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useBrainCreationSteps = () => { - const { t } = useTranslation("brain"); - const { isBrainCreationModalOpened, currentStep, setCurrentStep } = - useBrainCreationContext(); - - const steps: Step[] = [ - { - label: t("brain_type"), - value: "FIRST_STEP", - }, - { - label: t("brain_params"), - value: "SECOND_STEP", - }, - { - label: t("resources"), - value: "THIRD_STEP", - }, - ]; - - const currentStepIndex = steps.findIndex( - (step) => step.value === currentStep - ); - - useEffect(() => { - goToFirstStep(); - }, [isBrainCreationModalOpened]); - - const goToNextStep = () => { - if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) { - return; - } - const nextStep = steps[currentStepIndex + 1]; - - return setCurrentStep(nextStep.value); - }; - - const goToPreviousStep = () => { - if (currentStepIndex === -1 || currentStepIndex === 0) { - return; - } - const previousStep = steps[currentStepIndex - 1]; - - return setCurrentStep(previousStep.value); - }; - - const goToFirstStep = () => { - return setCurrentStep(steps[0].value); - }; - - return { - currentStep, - steps, - goToNextStep, - goToPreviousStep, - currentStepIndex, - }; -}; diff --git a/frontend/lib/components/AddBrainModal/index.ts b/frontend/lib/components/AddBrainModal/index.ts deleted file mode 100644 index a7a0db64c..000000000 --- a/frontend/lib/components/AddBrainModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./AddBrainModal"; diff --git a/frontend/lib/components/AddBrainModal/types/types.ts b/frontend/lib/components/AddBrainModal/types/types.ts deleted file mode 100644 index 6831c00c7..000000000 --- a/frontend/lib/components/AddBrainModal/types/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CreateBrainInput } from "@/lib/api/brain/types"; -import { iconList } from "@/lib/helpers/iconList"; - -const steps = ["FIRST_STEP", "SECOND_STEP", "THIRD_STEP"] as const; - -export type StepValue = (typeof steps)[number]; - -export type CreateBrainProps = CreateBrainInput & { - setDefault: boolean; - brainCreationStep: StepValue; -}; - -export interface BrainType { - name: string; - description: string; - snippet_emoji: string; - snippet_color: string; - iconName: keyof typeof iconList; - disabled?: boolean; - onClick?: () => void; -} diff --git a/frontend/lib/components/BrainSnippet/BrainSnippet.module.scss b/frontend/lib/components/BrainSnippet/BrainSnippet.module.scss deleted file mode 100644 index 179064dbf..000000000 --- a/frontend/lib/components/BrainSnippet/BrainSnippet.module.scss +++ /dev/null @@ -1,51 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_snippet_wrapper { - display: flex; - width: 375px; - flex-direction: column; - gap: Spacings.$spacing05; - justify-content: center; - align-items: center; - padding-top: Spacings.$spacing06; - box-shadow: BoxShadow.$large; - border-radius: Radius.$normal; - background-color: var(--background-0); - border: 1px solid var(--border-1); - - @media screen and (max-width: ScreenSizes.$small) { - width: 250px; - } - - .sample_wrapper { - display: flex; - justify-content: center; - align-items: center; - border-radius: Radius.$normal; - width: 64px; - height: 64px; - font-size: Typography.$very_large; - } - - .selector_wrapper { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .tabs { - width: 100%; - } - } - - .button { - align-self: flex-end; - padding: Spacings.$spacing05; - padding-top: 0; - } -} diff --git a/frontend/lib/components/BrainSnippet/BrainSnippet.tsx b/frontend/lib/components/BrainSnippet/BrainSnippet.tsx deleted file mode 100644 index 4ee9024d5..000000000 --- a/frontend/lib/components/BrainSnippet/BrainSnippet.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -import { Tab } from "@/lib/types/Tab"; - -import styles from "./BrainSnippet.module.scss"; - -import { ColorSelector } from "../ui/ColorSelector/ColorSelector"; -import EmojiSelector from "../ui/EmojiSelector/EmojiSelector"; -import { QuivrButton } from "../ui/QuivrButton/QuivrButton"; -import { Tabs } from "../ui/Tabs/Tabs"; - -export const BrainSnippet = ({ - setVisible, - initialColor, - initialEmoji, - onSave, -}: { - setVisible: React.Dispatch>; - initialColor?: string; - initialEmoji?: string; - onSave: (color: string, emoji: string) => Promise | void; -}): JSX.Element => { - const [color, setColor] = useState(initialColor); - const [emoji, setEmoji] = useState(initialEmoji); - const [selectedTab, setSelectedTab] = useState("Emoji"); - - const wrapperRef = useRef(null); - - const tabs: Tab[] = [ - { - label: "Emoji", - isSelected: selectedTab === "Emoji", - onClick: () => setSelectedTab("Emoji"), - iconName: "emoji", - }, - { - label: "Background", - isSelected: selectedTab === "Colors", - onClick: () => setSelectedTab("Colors"), - iconName: "color", - }, - ]; - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - wrapperRef.current && - !wrapperRef.current.contains(event.target as Node) - ) { - setVisible(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [setVisible]); - - return ( -
-
- {emoji} -
-
-
- -
- {selectedTab === "Emoji" && } - {selectedTab === "Colors" && ( - - )} -
-
- { - setVisible(false); - await onSave(color ?? "", emoji ?? ""); - }} - iconName="upload" - color="primary" - /> -
-
- ); -}; diff --git a/frontend/lib/components/ConnectionCards/ConnectionCards.module.scss b/frontend/lib/components/ConnectionCards/ConnectionCards.module.scss deleted file mode 100644 index 5df6219e3..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionCards.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; - -.connection_cards { - column-count: 2; - column-gap: Spacings.$spacing05; - padding-bottom: Spacings.$spacing05; - margin-bottom: -(Spacings.$spacing05); - - > * { - margin-bottom: Spacings.$spacing05; - } - - &.spaced { - justify-content: space-between; - } - - @media screen and (max-width: ScreenSizes.$small) { - column-count: 1; - } - - @media screen and (min-width: ScreenSizes.$large) { - column-count: 2; - } - - &.spaced { - column-count: 1; - - @media screen and (max-width: ScreenSizes.$small) { - column-count: 1; - } - } -} diff --git a/frontend/lib/components/ConnectionCards/ConnectionCards.tsx b/frontend/lib/components/ConnectionCards/ConnectionCards.tsx deleted file mode 100644 index 82c75ec1b..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionCards.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useSync } from "@/lib/api/sync/useSync"; - -import styles from "./ConnectionCards.module.scss"; -import { ConnectionSection } from "./ConnectionSection/ConnectionSection"; - -interface ConnectionCardsProps { - fromAddKnowledge?: boolean; -} - -export const ConnectionCards = ({ - fromAddKnowledge, -}: ConnectionCardsProps): JSX.Element => { - const { syncGoogleDrive, syncSharepoint, syncDropbox } = useSync(); - - return ( -
- syncDropbox(name)} - fromAddKnowledge={fromAddKnowledge} - /> - syncGoogleDrive(name)} - fromAddKnowledge={fromAddKnowledge} - /> - {/* syncNotion(name)} - fromAddKnowledge={fromAddKnowledge} - oneAccountLimitation={true} - /> */} - syncSharepoint(name)} - fromAddKnowledge={fromAddKnowledge} - /> -
- ); -}; diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.module.scss b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.module.scss deleted file mode 100644 index 003ca6f6e..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.connection_button_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - gap: Spacings.$spacing03; - - .left { - display: flex; - gap: Spacings.$spacing02; - overflow: hidden; - - .label { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - } - } - - .buttons_wrapper { - display: flex; - gap: Spacings.$spacing02; - } -} diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.tsx b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.tsx deleted file mode 100644 index 461bfa00f..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionButton/ConnectionButton.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Sync, SyncStatus } from "@/lib/api/sync/types"; -import { ConnectionIcon } from "@/lib/components/ui/ConnectionIcon/ConnectionIcon"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -import styles from "./ConnectionButton.module.scss"; - -interface ConnectionButtonProps { - label: string; - index: number; - onClick: (id: number) => void; - submitted?: boolean; - sync: Sync; -} - -export const ConnectionButton = ({ - label, - index, - onClick, - submitted, - sync, -}: ConnectionButtonProps): JSX.Element => { - const { supabase } = useSupabase(); - const [status, setStatus] = useState(sync.status); - - const handleStatusChange = (payload: { new: Sync }) => { - if (payload.new.id === sync.id) { - setStatus(payload.new.status); - } - }; - - useEffect(() => { - setStatus(sync.status); - const channel = supabase - .channel("syncs_user") - .on( - "postgres_changes", - { event: "UPDATE", schema: "public", table: "syncs_user" }, - handleStatusChange - ) - .subscribe(); - - return () => { - void supabase.removeChannel(channel); - }; - }, []); - - return ( -
-
- - {label} -
-
- onClick(index)} - disabled={status === "SYNCING"} - /> -
-
- ); -}; diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.module.scss b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.module.scss deleted file mode 100644 index f4b198807..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.connection_line_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - - .left { - display: flex; - gap: Spacings.$spacing02; - overflow: hidden; - - .label { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - } - } - - .icons { - display: flex; - gap: Spacings.$spacing02; - } -} - -.modal_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - padding-inline: Spacings.$spacing06; - - .modal_title { - display: flex; - margin-left: -(Spacings.$spacing08); - gap: Spacings.$spacing06; - } - - .buttons_wrapper { - display: flex; - justify-content: space-between; - } -} diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.tsx b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.tsx deleted file mode 100644 index 9fc1c57e1..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionLine/ConnectionLine.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useState } from "react"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { useSync } from "@/lib/api/sync/useSync"; -import { ConnectionIcon } from "@/lib/components/ui/ConnectionIcon/ConnectionIcon"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton"; - -import styles from "./ConnectionLine.module.scss"; - -interface ConnectionLineProps { - label: string; - index: number; - id: number; - warnUserOnDelete?: boolean; -} - -export const ConnectionLine = ({ - label, - index, - id, - warnUserOnDelete, -}: ConnectionLineProps): JSX.Element => { - const [deleteLoading, setDeleteLoading] = useState(false); - const [deleteModalOpened, setDeleteModalOpened] = useState(false); - - const { deleteUserSync } = useSync(); - const { setHasToReload } = useFromConnectionsContext(); - - return ( - <> -
-
- - {label} -
-
- { - if (warnUserOnDelete) { - setDeleteModalOpened(true); - } else { - await deleteUserSync(id); - setHasToReload(true); - } - }} - /> -
-
- } - CloseTrigger={
} - > -
-
-
- -
- - It takes up to 24 hours to delete this connection. Are you sure - you want to proceed? - -
-
- setDeleteModalOpened(false)} - /> - { - setDeleteLoading(true); - await deleteUserSync(id); - setDeleteLoading(false); - setHasToReload(true); - setDeleteModalOpened(false); - }} - /> -
-
- - - ); -}; diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.module.scss b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.module.scss deleted file mode 100644 index 1cc10b89c..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.module.scss +++ /dev/null @@ -1,96 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Transitions.module.scss"; -@use "styles/Typography.module.scss"; - -.connection_section_wrapper { - padding: Spacings.$spacing05; - border-radius: Radius.$normal; - overflow: hidden; - border-bottom: 1px solid transparent; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - border-radius: Radius.$normal; - border: 1px solid var(--border-0); - height: min-content; - width: 100%; - max-width: 800px; - - @media (max-width: ScreenSizes.$small) { - width: 100%; - } - - .connection_section_header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding-block: Spacings.$spacing03; - @include Typography.H3; - - .left { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - } - - .icon_rotate { - transition: transform 0.3s Transitions.$easeOutBack; - } - - .icon_rotate_down { - transform: rotate(0deg); - } - - .icon_rotate_right { - transform: rotate(-90deg); - - .label { - @include Typography.H3; - } - } - - .deleting_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - - .deleting_mention { - color: var(--warning); - font-size: Typography.$tiny; - } - } - } - - .existing_connections { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - width: 100%; - padding-top: Spacings.$spacing05; - - .existing_connections_header { - display: flex; - justify-content: space-between; - align-items: center; - - .label { - font-weight: 500; - font-size: Typography.$tiny; - } - } - - .folded { - display: flex; - padding-left: Spacings.$spacing03; - - .negative_margin { - margin-left: -(Spacings.$spacing02); - } - } - } -} diff --git a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.tsx b/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.tsx deleted file mode 100644 index 3d9b7ae98..000000000 --- a/frontend/lib/components/ConnectionCards/ConnectionSection/ConnectionSection.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import Image from "next/image"; -import { useEffect, useState } from "react"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { OpenedConnection, Provider, Sync } from "@/lib/api/sync/types"; -import { useSync } from "@/lib/api/sync/useSync"; -import { QuivrButton } from "@/lib/components/ui/QuivrButton/QuivrButton"; -import { iconList } from "@/lib/helpers/iconList"; - -import { ConnectionButton } from "./ConnectionButton/ConnectionButton"; -import { ConnectionLine } from "./ConnectionLine/ConnectionLine"; -import styles from "./ConnectionSection.module.scss"; - -import { ConnectionIcon } from "../../ui/ConnectionIcon/ConnectionIcon"; -import { Icon } from "../../ui/Icon/Icon"; -import { TextButton } from "../../ui/TextButton/TextButton"; -import Tooltip from "../../ui/Tooltip/Tooltip"; - -interface ConnectionSectionProps { - label: string; - provider: Provider; - callback: (name: string) => Promise<{ authorization_url: string }>; - fromAddKnowledge?: boolean; - oneAccountLimitation?: boolean; -} - -export const ConnectionSection = ({ - label, - provider, - fromAddKnowledge, - callback, - oneAccountLimitation, -}: ConnectionSectionProps): JSX.Element => { - const { providerIconUrls, getUserSyncs, getSyncFiles } = useSync(); - const { - setCurrentSyncElements, - setCurrentSyncId, - setOpenedConnections, - openedConnections, - hasToReload, - setHasToReload, - setLoadingFirstList, - setCurrentProvider, - } = useFromConnectionsContext(); - const [existingConnections, setExistingConnections] = useState([]); - const [folded, setFolded] = useState(!fromAddKnowledge); - - const fetchUserSyncs = async () => { - try { - const res: Sync[] = await getUserSyncs(); - setExistingConnections( - res.filter( - (sync) => - Object.keys(sync.credentials).length !== 0 && - sync.provider === provider - ) - ); - } catch (error) { - console.error(error); - } - }; - - const getButtonIcon = (): keyof typeof iconList => { - return existingConnections.filter( - (connection) => connection.status !== "REMOVED" - ).length > 0 - ? "add" - : "sync"; - }; - - const getButtonName = (): string => { - return existingConnections.filter( - (connection) => connection.status !== "REMOVED" - ).length > 0 - ? "Add more" - : "Connect"; - }; - - useEffect(() => { - void fetchUserSyncs(); - }, []); - - useEffect(() => { - const handleVisibilityChange = () => { - if (document.visibilityState === "visible" && !document.hidden) { - void fetchUserSyncs(); - } - }; - - document.addEventListener("visibilitychange", handleVisibilityChange); - - return () => { - document.removeEventListener("visibilitychange", handleVisibilityChange); - }; - }, []); - - useEffect(() => { - if (hasToReload) { - void fetchUserSyncs(); - setHasToReload(false); - } - }, [hasToReload]); - - const handleOpenedConnections = (userSyncId: number) => { - const existingConnection = openedConnections.find( - (connection) => connection.user_sync_id === userSyncId - ); - - if (!existingConnection) { - const newConnection: OpenedConnection = { - name: - existingConnections.find((connection) => connection.id === userSyncId) - ?.name ?? "", - user_sync_id: userSyncId, - id: undefined, - provider: provider, - submitted: false, - selectedFiles: { files: [] }, - last_synced: "", - }; - - setOpenedConnections([...openedConnections, newConnection]); - } - }; - - const handleGetSyncFiles = async (userSyncId: number) => { - try { - setLoadingFirstList(true); - const res = await getSyncFiles(userSyncId); - setLoadingFirstList(false); - setCurrentSyncElements(res); - setCurrentSyncId(userSyncId); - handleOpenedConnections(userSyncId); - } catch (error) { - console.error("Failed to get sync files:", error); - } - }; - - const connect = async () => { - const res = await callback( - Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15) - ); - if (res.authorization_url) { - window.open(res.authorization_url, "_blank"); - } - }; - - const renderConnectionLines = ( - connections: Sync[], - connectionFolded: boolean - ) => { - if (!connectionFolded) { - return connections - .filter((connection) => connection.status !== "REMOVED") - .map((connection, index) => ( - - )); - } else { - return ( -
- {connections.map((connection, index) => ( - - ))} -
- ); - } - }; - - const renderExistingConnections = () => { - const activeConnections = existingConnections.filter( - (connection) => connection.status !== "REMOVED" - ); - - if (activeConnections.length === 0) { - return null; - } - - if (!fromAddKnowledge) { - return ( -
-
- Connected accounts - setFolded(!folded)} - /> -
- {renderConnectionLines(activeConnections, folded)} -
- ); - } else { - return ( -
- {activeConnections.map((connection, index) => ( - - openedConnection.name === connection.name && - openedConnection.submitted - )} - onClick={() => { - void handleGetSyncFiles(connection.id); - setCurrentProvider(connection.provider); - }} - sync={connection} - /> - ))} -
- ); - } - }; - - return ( - <> -
-
-
- {label} - {label} -
- {!fromAddKnowledge && - (!oneAccountLimitation || existingConnections.length === 0) ? ( - - ) : existingConnections[0] && - existingConnections[0].status === "REMOVED" ? ( - -
- - Deleting -
-
- ) : null} - - {fromAddKnowledge && - (!oneAccountLimitation || existingConnections.length === 0) && ( - - )} -
- {renderExistingConnections()} -
- - ); -}; diff --git a/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss b/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss deleted file mode 100644 index ed409dd19..000000000 --- a/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss +++ /dev/null @@ -1,92 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -%header_style { - background-color: var(--background-2); - padding-inline: Spacings.$spacing05; - padding-block: Spacings.$spacing01; - font-size: Typography.$small; -} - -@mixin textColor($color) { - color: var(--#{$color}); -} - -.current_brain_wrapper { - @extend %header_style; - @include textColor(text-1); - - .brain_infos { - display: flex; - justify-content: space-between; - align-items: center; - overflow: hidden; - - .left { - display: flex; - gap: Spacings.$spacing02; - align-items: center; - @include Typography.EllipsisOverflow; - - .title, - .brain_name { - white-space: nowrap; - } - - .brain_name_wrapper { - display: flex; - gap: Spacings.$spacing02; - align-items: center; - overflow: hidden; - - .dark_image { - filter: invert(100%); - } - - .brain_image { - border-radius: Radius.$normal; - } - - .brain_name { - @include textColor(text-3); - @include Typography.EllipsisOverflow; - - &.new { - color: var(--primary-0); - } - } - - .brain_snippet { - min-width: 18px; - min-height: 18px; - max-width: 18px; - max-height: 18px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$very_tiny; - } - - .warning { - color: var(--warning); - display: flex; - align-items: center; - gap: Spacings.$spacing02; - margin-left: Spacings.$spacing02; - font-size: Typography.$tiny; - } - } - } - } -} - -.no_brain_selected, -.no_credits_left { - @extend %header_style; -} - -.no_credits_left { - @include textColor(dangerous); -} diff --git a/frontend/lib/components/CurrentBrain/CurrentBrain.tsx b/frontend/lib/components/CurrentBrain/CurrentBrain.tsx deleted file mode 100644 index 48b5adf75..000000000 --- a/frontend/lib/components/CurrentBrain/CurrentBrain.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import Image from "next/image"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; - -import styles from "./CurrentBrain.module.scss"; - -import { Icon } from "../ui/Icon/Icon"; -import { LoaderIcon } from "../ui/LoaderIcon/LoaderIcon"; - -interface CurrentBrainProps { - allowingRemoveBrain: boolean; - remainingCredits: number | null; - isNewBrain?: boolean; -} - -const BrainNameAndImage = ({ - currentBrain, - isNewBrain, -}: { - currentBrain: MinimalBrainForUser; - isNewBrain: boolean; -}) => { - return ( - <> - {currentBrain.image_url ? ( - - ) : ( -
- {currentBrain.snippet_emoji} -
- )} - - {currentBrain.display_name ?? currentBrain.name} - - - ); -}; - -const ProcessingNotification = ({ - currentBrain, - bulkNotifications, -}: { - currentBrain?: MinimalBrainForUser; - bulkNotifications: Array<{ - brain_id: string; - notifications: Array<{ status: string }>; - }>; -}) => { - const isProcessing = - currentBrain && - bulkNotifications.some( - (bulkNotif) => - bulkNotif.brain_id === currentBrain.id && - bulkNotif.notifications.some((notif) => notif.status === "info") - ); - - return ( - isProcessing && ( -
- - Processing knowledges -
- ) - ); -}; - -export const CurrentBrain = ({ - allowingRemoveBrain, - remainingCredits, - isNewBrain, -}: CurrentBrainProps): JSX.Element => { - const { currentBrain, setCurrentBrainId } = useBrainContext(); - const { bulkNotifications } = useNotificationsContext(); - - const removeCurrentBrain = (): void => { - setCurrentBrainId(null); - }; - - if (remainingCredits === 0) { - return ( -
- - You’ve run out of credits! Upgrade your plan now to continue chatting. - -
- ); - } - - if (!currentBrain) { - return <>; - } - - return ( -
-
-
- Talking to -
- - -
-
- {allowingRemoveBrain && ( -
{ - event.nativeEvent.stopImmediatePropagation(); - removeCurrentBrain(); - }} - > - -
- )} -
-
- ); -}; diff --git a/frontend/lib/components/HelpWindow/HelpWindow.module.scss b/frontend/lib/components/HelpWindow/HelpWindow.module.scss deleted file mode 100644 index 166ecd2a9..000000000 --- a/frontend/lib/components/HelpWindow/HelpWindow.module.scss +++ /dev/null @@ -1,94 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.help_wrapper { - position: absolute; - height: 100vh; - right: 0; - width: 0; - z-index: ZIndexes.$modal + 1; - box-shadow: BoxShadow.$medium; - background-color: var(--background-0); - overflow: hidden; - transition: width 0.2s ease-in-out; - overflow-y: auto; - - &.visible { - width: 600px; - - .header { - display: block; - display: flex; - align-items: center; - justify-content: space-between; - padding: Spacings.$spacing05; - border-bottom: 1px solid var(--border-1); - - .title { - @include Typography.H1; - } - } - - .content { - padding: Spacings.$spacing05; - display: block; - - .section { - display: flex; - flex-direction: column; - - .title { - @include Typography.H2; - } - - .section_content { - padding: Spacings.$spacing05; - padding-inline: Spacings.$spacing06; - font-size: Typography.$small; - - ul { - padding: Spacings.$spacing05; - li { - padding-block: Spacings.$spacing03; - - .connection { - display: inline-flex; - align-items: center; - gap: Spacings.$spacing02; - padding-right: Spacings.$spacing02; - - .pre { - white-space: pre; - } - } - } - } - } - - .image { - display: flex; - justify-content: center; - width: 100%; - padding-top: Spacings.$spacing05; - } - } - } - } - - .header { - display: none; - } - - .content { - display: none; - } - - @media screen and (max-width: ScreenSizes.$small) { - &.visible { - width: 100%; - } - } -} diff --git a/frontend/lib/components/HelpWindow/HelpWindow.tsx b/frontend/lib/components/HelpWindow/HelpWindow.tsx deleted file mode 100644 index 78618c64f..000000000 --- a/frontend/lib/components/HelpWindow/HelpWindow.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import Image from "next/image"; -import { useEffect, useRef, useState } from "react"; - -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import styles from "./HelpWindow.module.scss"; - -import { Icon } from "../ui/Icon/Icon"; - -export const HelpWindow = (): JSX.Element => { - const { isVisible, setIsVisible } = useHelpContext(); - const [loadingOnboarded, setLoadingOnboarded] = useState(false); - const helpWindowRef = useRef(null); - const { userIdentityData } = useUserData(); - const { updateUserIdentity } = useUserApi(); - - const closeHelpWindow = async () => { - setIsVisible(false); - if (userIdentityData && !userIdentityData.onboarded) { - setLoadingOnboarded(true); - await updateUserIdentity({ - ...userIdentityData, - username: userIdentityData.username, - onboarded: true, - }); - } - }; - - useEffect(() => { - if ( - userIdentityData?.username && - !userIdentityData.onboarded && - !loadingOnboarded - ) { - setIsVisible(true); - } - }); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - helpWindowRef.current && - !helpWindowRef.current.contains(event.target as Node) - ) { - void (async () => { - try { - await closeHelpWindow(); - } catch (error) { - console.error("Error while closing help window", error); - } - })(); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }); - - return ( -
-
- 🧠 What is Quivr ? - closeHelpWindow()} - /> -
-
-
- 🧱 Build your second brains - - A Brain in Quivr is an advanced knowledge system - designed to integrate and leverage information from various sources. -
    -
  • - 📠Knowledge Integration -
    Connect to and pull data from various platforms like{" "} - - Google Drive{" "} - Google Drive - - , - - SharePoint{" "} - SharePoint - - , and{""} - - Dropbox{" "} - Dropbox - - . You can also incorporate data from URLs and{" "} - files. -
  • -
  • - 🤖 AI Models -
    Utilize powerful models such as{" "} - - GPT - {" "} - and{" "} - - Mistral - {" "} - to process and understand the integrated knowledge. -
  • -
  • - 🔧 Customization -
    Tailor the behavior of your Brain with{" "} - custom prompts and settings, such as{" "} - max tokens, to better suit your needs. -
  • -
-

- You can also share your brains with other Quivr - users, allowing them to access and benefit from your knowledge - systems. 🤠-

-
-
-
- 🤖 Talk to AI Models - -

- Quivr allows you to interact directly with AI - models such as{" "} - - GPT-4 - {" "} - and{" "} - - Mistral - - . Simply start a conversation with the AI to get answers and - support based on a broad range of data and knowledge. 🤖✨ -

-
-
-
-
🎯 Select assistant
- -

- Press - @ to choose the AI model or the Brain you want - to interact with. -

-
-
- Quivr -
-
-
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/KnowledgeToFeedInput.tsx b/frontend/lib/components/KnowledgeToFeedInput/KnowledgeToFeedInput.tsx deleted file mode 100644 index 0880a43d4..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/KnowledgeToFeedInput.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import Button from "@/lib/components/ui/Button"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; - -import { FeedItems } from "./components"; -import { Crawler } from "./components/Crawler"; -import { FileUploader } from "./components/FileUploader"; - -export const KnowledgeToFeedInput = ({ - feedBrain, -}: { - feedBrain: () => void; -}): JSX.Element => { - const { t } = useTranslation(["translation", "upload"]); - - const { knowledgeToFeed } = useKnowledgeToFeedContext(); - - return ( -
-
- - - {`${t("and", { ns: "translation" })} / ${t("or", { - ns: "translation", - })}`} - - -
- -
- -
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/helpers/isValidUrl.ts b/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/helpers/isValidUrl.ts deleted file mode 100644 index b41a47ef4..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/helpers/isValidUrl.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const isValidUrl = (url: string): boolean => { - try { - new URL(url); - - return true; - } catch (err) { - return false; - } -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/hooks/useCrawler.ts b/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/hooks/useCrawler.ts deleted file mode 100644 index 63885f89a..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/hooks/useCrawler.ts +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; -import { useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useToast } from "@/lib/hooks"; -import { useOnboarding } from "@/lib/hooks/useOnboarding"; -import { useOnboardingTracker } from "@/lib/hooks/useOnboardingTracker"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; -import { useEventTracking } from "@/services/analytics/june/useEventTracking"; - -import { isValidUrl } from "../helpers/isValidUrl"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useCrawler = () => { - const { addKnowledgeToFeed } = useKnowledgeToFeedContext(); - const urlInputRef = useRef(null); - const { session } = useSupabase(); - const { publish } = useToast(); - const { t } = useTranslation(["translation", "upload"]); - const [urlToCrawl, setUrlToCrawl] = useState(""); - const { track } = useEventTracking(); - const { trackOnboardingEvent } = useOnboardingTracker(); - const { isOnboarding } = useOnboarding(); - - if (session === null) { - redirectToLogin(); - } - - const handleSubmit = () => { - if (urlToCrawl === "") { - return; - } - if (!isValidUrl(urlToCrawl)) { - void track("URL_INVALID"); - publish({ - variant: "danger", - text: t("invalidUrl"), - }); - - return; - } - if (isOnboarding) { - void trackOnboardingEvent("URL_CRAWLED"); - } else { - void track("URL_CRAWLED"); - } - addKnowledgeToFeed({ - source: "crawl", - url: urlToCrawl, - }); - setUrlToCrawl(""); - }; - - return { - urlInputRef, - urlToCrawl, - setUrlToCrawl, - handleSubmit, - }; -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/index.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/index.tsx deleted file mode 100644 index ec5c265be..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/Crawler/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; -import { useTranslation } from "react-i18next"; -import { MdSend } from "react-icons/md"; - -import Button from "@/lib/components/ui/Button"; -import Field from "@/lib/components/ui/Field"; - -import { useCrawler } from "./hooks/useCrawler"; - -export const Crawler = (): JSX.Element => { - const { urlInputRef, urlToCrawl, handleSubmit, setUrlToCrawl } = useCrawler(); - const { t } = useTranslation(["translation", "upload"]); - - return ( -
-
-
{ - e.preventDefault(); - handleSubmit(); - }} - className="w-full" - > - setUrlToCrawl(e.target.value)} - icon={ - - } - /> - -
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/FeedItems.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/FeedItems.tsx deleted file mode 100644 index d8c78d5e5..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/FeedItems.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Fragment } from "react"; - -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; - -import { CrawlFeedItem } from "./components/CrawlFeedItem"; -import { FileFeedItem } from "./components/FileFeedItem"; - -export const FeedItems = (): JSX.Element => { - const { knowledgeToFeed, removeKnowledgeToFeed } = - useKnowledgeToFeedContext(); - if (knowledgeToFeed.length === 0) { - return ; - } - - return ( -
- {knowledgeToFeed.map((item, index) => - item.source === "crawl" ? ( - removeKnowledgeToFeed(index)} - /> - ) : ( - removeKnowledgeToFeed(index)} - /> - ) - )} -
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/CrawlFeedItem.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/CrawlFeedItem.tsx deleted file mode 100644 index 4f97f9bd0..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/CrawlFeedItem.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { IoMdCloseCircle } from "react-icons/io"; -import { MdLink } from "react-icons/md"; - -import { FeedTitleDisplayer } from "./FeedTitleDisplayer"; - -import { StyledFeedItemDiv } from "../styles/StyledFeedItemDiv"; - -type CrawlFeedItemProps = { - url: string; - onRemove: () => void; -}; -export const CrawlFeedItem = ({ - url, - onRemove, -}: CrawlFeedItemProps): JSX.Element => { - return ( - -
-
- -
-
- -
-
-
- -
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx deleted file mode 100644 index 702077c65..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; - -import { enhanceUrlDisplay } from "./utils/enhanceUrlDisplay"; -import { removeFileExtension } from "./utils/removeFileExtension"; - -type FeedTitleDisplayerProps = { - title: string; - isUrl?: boolean; -}; - -export const FeedTitleDisplayer = ({ - title, - isUrl = false, -}: FeedTitleDisplayerProps): JSX.Element => { - return ( -
- -

- {isUrl ? enhanceUrlDisplay(title) : removeFileExtension(title)} -

-
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/index.ts b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/index.ts deleted file mode 100644 index 7b4426407..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./FeedTitleDisplayer"; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/enhanceUrlDisplay.ts b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/enhanceUrlDisplay.ts deleted file mode 100644 index ef491720f..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/enhanceUrlDisplay.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const enhanceUrlDisplay = (url: string): string => { - const parts = url.split("/"); - - // Check if the URL has at least 3 parts (protocol, domain, and one more segment) - if (parts.length >= 3) { - const domain = parts[2]; - const path = parts.slice(3).join("/"); - - // Split the domain by "." to check for subdomains and remove "www" - const domainParts = domain.split("."); - if (domainParts[0] === "www") { - domainParts.shift(); // Remove "www" - } - - // Combine the beginning (subdomain/domain) and the end (trimmed path) - const beginning = domainParts.join("."); - const trimmedPath = path.slice(0, 5) + "..." + path.slice(-5); // Display the beginning and end of the path - - return `${beginning}/${trimmedPath}`; - } - - // If the URL doesn't have enough parts, return it as is - return url; -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/removeFileExtension.ts b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/removeFileExtension.ts deleted file mode 100644 index b8328dbe5..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/utils/removeFileExtension.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const removeFileExtension = (fileName: string): string => { - const lastDotIndex = fileName.lastIndexOf("."); - if (lastDotIndex !== -1) { - return fileName.substring(0, lastDotIndex); - } - - return fileName; -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FileFeedItem.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FileFeedItem.tsx deleted file mode 100644 index 01d9b4c07..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FileFeedItem.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { IoMdCloseCircle } from "react-icons/io"; - -import { getFileIcon } from "@/lib/helpers/getFileIcon"; - -import { FeedTitleDisplayer } from "./FeedTitleDisplayer"; - -import { StyledFeedItemDiv } from "../styles/StyledFeedItemDiv"; - -type FileFeedItemProps = { - file: File; - onRemove: () => void; -}; - -export const FileFeedItem = ({ - file, - onRemove, -}: FileFeedItemProps): JSX.Element => { - const icon = getFileIcon(file.name); - - return ( - -
-
{icon}
-
- -
-
-
- -
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/index.ts b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/index.ts deleted file mode 100644 index 7b4426407..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./FeedTitleDisplayer"; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/index.ts b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/index.ts deleted file mode 100644 index 0096f92ff..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./FeedItems"; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/styles/StyledFeedItemDiv.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/styles/StyledFeedItemDiv.tsx deleted file mode 100644 index 31cf0d628..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/styles/StyledFeedItemDiv.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { HtmlHTMLAttributes } from "react"; - -import { cn } from "@/lib/utils"; - -type StyledFeedItemDivProps = HtmlHTMLAttributes; -export const StyledFeedItemDiv = ({ - className, - ...propsWithoutClassname -}: StyledFeedItemDivProps): JSX.Element => ( -
-); diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FileUploader/index.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FileUploader/index.tsx deleted file mode 100644 index 1991e65a5..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FileUploader/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; -import { useTranslation } from "react-i18next"; -import { IoCloudUploadOutline } from "react-icons/io5"; - -import Card from "@/lib/components/ui/Card"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useCustomDropzone } from "@/lib/hooks/useDropzone"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; - -export const FileUploader = (): JSX.Element => { - const { session } = useSupabase(); - const { getInputProps, isDragActive, open } = useCustomDropzone(); - - if (session === null) { - redirectToLogin(); - } - - const { t } = useTranslation(["translation", "upload"]); - - return ( -
-
-
- - - - {isDragActive && ( -

{t("drop", { ns: "upload" })}

- )} -
-
-
-
- ); -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/index.ts b/frontend/lib/components/KnowledgeToFeedInput/components/index.ts deleted file mode 100644 index f2d053e24..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./Crawler"; -export * from "./FeedItems"; -export * from "./FileUploader"; diff --git a/frontend/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts.ts b/frontend/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts.ts deleted file mode 100644 index 220eea40f..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { UUID } from "crypto"; -import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; - -import { useCrawlApi } from "@/lib/api/crawl/useCrawlApi"; -import { useUploadApi } from "@/lib/api/upload/useUploadApi"; -import { getAxiosErrorParams } from "@/lib/helpers/getAxiosErrorParams"; -import { useToast } from "@/lib/hooks"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useKnowledgeToFeedInput = () => { - const { publish } = useToast(); - const { uploadFile } = useUploadApi(); - const { t } = useTranslation(["upload"]); - const { crawlWebsiteUrl } = useCrawlApi(); - - const crawlWebsiteHandler = useCallback( - async (url: string, brainId: UUID, bulk_id: UUID, chat_id?: UUID) => { - // Configure parameters - const config = { - url: url, - js: false, - depth: 1, - max_pages: 100, - max_time: 60, - }; - - try { - await crawlWebsiteUrl({ - brainId, - config, - chat_id, - bulk_id, - }); - } catch (error: unknown) { - const errorParams = getAxiosErrorParams(error); - if (errorParams !== undefined) { - publish({ - variant: "danger", - text: t("crawlFailed", { - message: JSON.stringify(errorParams.message), - }), - }); - } else { - publish({ - variant: "danger", - text: t("crawlFailed", { - message: JSON.stringify(error), - }), - }); - } - } - }, - [crawlWebsiteUrl, publish, t] - ); - - const uploadFileHandler = useCallback( - async (file: File, brainId: UUID, bulk_id: UUID, chat_id?: UUID) => { - const formData = new FormData(); - formData.append("uploadFile", file); - try { - await uploadFile({ - brainId, - formData, - chat_id, - bulk_id, - }); - } catch (e: unknown) { - const errorParams = getAxiosErrorParams(e); - if (errorParams !== undefined) { - publish({ - variant: "danger", - text: t("uploadFailed", { - message: JSON.stringify(errorParams.message), - }), - }); - } else { - publish({ - variant: "danger", - text: t("uploadFailed", { - message: JSON.stringify(e), - }), - }); - } - } - }, - [publish, t, uploadFile] - ); - - return { - crawlWebsiteHandler, - uploadFileHandler, - }; -}; diff --git a/frontend/lib/components/KnowledgeToFeedInput/index.ts b/frontend/lib/components/KnowledgeToFeedInput/index.ts deleted file mode 100644 index 8f1207bd3..000000000 --- a/frontend/lib/components/KnowledgeToFeedInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./KnowledgeToFeedInput"; diff --git a/frontend/lib/components/Menu/Menu.module.scss b/frontend/lib/components/Menu/Menu.module.scss deleted file mode 100644 index 70b060b0f..000000000 --- a/frontend/lib/components/Menu/Menu.module.scss +++ /dev/null @@ -1,87 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Variables.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.menu_container { - background-color: var(--background-1); - border-right: 1px solid var(--border-1); - width: Variables.$menuWidth; - transition: width 0.1s ease-in-out; - - &.hidden { - width: 0; - } - - .menu_wrapper { - padding-top: Spacings.$spacing05; - display: flex; - flex-direction: column; - height: 100vh; - overflow-y: auto; - overflow-x: hidden; - - .quivr_logo_wrapper { - cursor: pointer; - margin-inline: auto; - display: flex; - justify-content: center; - } - - .buttons_wrapper { - display: flex; - flex-direction: column; - justify-content: space-between; - padding: Spacings.$spacing05; - flex-grow: 1; - gap: Spacings.$spacing05; - - .block { - display: flex; - flex-direction: column; - gap: Spacings.$spacing04; - } - } - - .social_buttons_wrapper { - padding-block: Spacings.$spacing04; - border-top: 1px solid var(--border-1); - } - } -} - -.menu_control_button_wrapper { - background-color: transparent; - position: absolute; - top: Spacings.$spacing05; - left: Spacings.$spacing05; - transition: margin-left 0.2s ease-in-out; - z-index: ZIndexes.$overlay; - - &.shifted { - margin-left: 180px; - } -} - -.notifications_panel { - width: 400px; - position: absolute; - top: Variables.$pageHeaderHeight; - min-height: calc(100% - Variables.$pageHeaderHeight); - max-height: calc(100% - Variables.$pageHeaderHeight); - overflow: auto; - left: calc(Variables.$menuWidth); - z-index: ZIndexes.$overlay; - border-right: 1px solid var(--border-1); - box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1); - background-color: var(--background-0); - - @media (max-width: ScreenSizes.$small) { - width: 100vw; - left: 0; - min-height: 100vh; - max-height: 100vh; - top: 0; - } -} diff --git a/frontend/lib/components/Menu/Menu.tsx b/frontend/lib/components/Menu/Menu.tsx deleted file mode 100644 index 3dd310737..000000000 --- a/frontend/lib/components/Menu/Menu.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { MotionConfig } from "framer-motion"; -import { usePathname, useRouter } from "next/navigation"; -import { useFeatureFlagEnabled } from "posthog-js/react"; -import { useState } from "react"; - -import { MenuControlButton } from "@/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton"; -import { useChatsList } from "@/app/chat/[chatId]/hooks/useChatsList"; -import { QuivrLogo } from "@/lib/assets/QuivrLogo"; -import { nonProtectedPaths } from "@/lib/config/routesConfig"; -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import styles from "./Menu.module.scss"; -import { AnimatedDiv } from "./components/AnimationDiv"; -import { DiscussionButton } from "./components/DiscussionButton/DiscussionButton"; -import { HomeButton } from "./components/HomeButton/HomeButton"; -import { Notifications } from "./components/Notifications/Notifications"; -import { NotificationsButton } from "./components/NotificationsButton/NotificationsButton"; -import { ProfileButton } from "./components/ProfileButton/ProfileButton"; -import { QualityAssistantButton } from "./components/QualityAssistantButton/QualityAssistantButton"; -import { SocialsButtons } from "./components/SocialsButtons/SocialsButtons"; -import { StudioButton } from "./components/StudioButton/StudioButton"; -import { ThreadsButton } from "./components/ThreadsButton/ThreadsButton"; -import { UpgradeToPlusButton } from "./components/UpgradeToPlusButton/UpgradeToPlusButton"; - -const showUpgradeButton = process.env.NEXT_PUBLIC_SHOW_TOKENS === "true"; - -export const Menu = (): JSX.Element => { - const { isOpened } = useMenuContext(); - const { isVisible } = useNotificationsContext(); - const router = useRouter(); - const pathname = usePathname() ?? ""; - const [isLogoHovered, setIsLogoHovered] = useState(false); - const { isDarkMode } = useUserSettingsContext(); - const flagEnabled = useFeatureFlagEnabled("show-quality-assistant"); - - useChatsList(); - - if (nonProtectedPaths.includes(pathname)) { - return <>; - } - - const displayedOnPages = [ - "/assistants", - "/chat", - "/library", - "/search", - "studio", - "/quality-assistant", - "/user", - ]; - - const isMenuDisplayed = displayedOnPages.some((page) => - pathname.includes(page) - ); - - if (!isMenuDisplayed) { - return <>; - } - - return ( -
- -
- -
-
router.push("/search")} - onMouseEnter={() => setIsLogoHovered(true)} - onMouseLeave={() => setIsLogoHovered(false)} - > - -
- -
-
- - - - {flagEnabled && } - - - -
-
- {!!showUpgradeButton && } - -
-
-
- -
-
-
-
-
- -
-
- {isVisible && ( -
- -
- )} -
- ); -}; diff --git a/frontend/lib/components/Menu/components/AnimationDiv.tsx b/frontend/lib/components/Menu/components/AnimationDiv.tsx deleted file mode 100644 index dd94950fa..000000000 --- a/frontend/lib/components/Menu/components/AnimationDiv.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { motion } from "framer-motion"; - -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; - -type AnimatedDivProps = { - children: React.ReactNode; -}; -export const AnimatedDiv = ({ children }: AnimatedDivProps): JSX.Element => { - const { isOpened } = useMenuContext(); - const OPENED_MENU_WIDTH = 230; - - return ( - - {children} - - ); -}; diff --git a/frontend/lib/components/Menu/components/AssistantsButton/AssistantsButton.module.scss b/frontend/lib/components/Menu/components/AssistantsButton/AssistantsButton.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/components/Menu/components/AssistantsButton/AssistantsButton.tsx b/frontend/lib/components/Menu/components/AssistantsButton/AssistantsButton.tsx deleted file mode 100644 index 67e0ca816..000000000 --- a/frontend/lib/components/Menu/components/AssistantsButton/AssistantsButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; - -export const AssistantsButton = (): JSX.Element => { - const pathname = usePathname() ?? ""; - const isSelected = pathname.includes("/assistants"); - - return ( - - - - ); -}; diff --git a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss b/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss deleted file mode 100644 index 72f5242de..000000000 --- a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/IconSizes.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; - -.button_wrapper { - display: flex; - padding: Spacings.$spacing03; - justify-content: space-between; - align-items: center; - border: 1px solid var(--border-0); - border-radius: Radius.$big; - background-color: var(--background-0); - cursor: pointer; - color: var(--text-2); - transition: box-shadow 0.3s ease; - - .left_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .shortcuts_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing01; - - .shortcut { - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - border: 1px solid var(--border-0); - border-radius: Radius.$small; - width: 16px; - height: 16px; - } - } - } - - &:hover { - border-color: var(--primary-0); - box-shadow: BoxShadow.$primary; - } -} diff --git a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.tsx b/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.tsx deleted file mode 100644 index eb1e94400..000000000 --- a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; - -import styles from "./DiscussionButton.module.scss"; - -export const DiscussionButton = (): JSX.Element => { - const [hovered, setHovered] = useState(false); - const { setIsVisible } = useSearchModalContext(); - - const handleClick = (event: React.MouseEvent) => { - setIsVisible(true); - event.nativeEvent.stopImmediatePropagation(); - }; - - return ( -
setHovered(true)} - onMouseLeave={() => setHovered(false)} - onClick={handleClick} - > -
- New Thread -
-
⌘
-
K
-
-
- -
- ); -}; diff --git a/frontend/lib/components/Menu/components/HomeButton/HomeButton.module.scss b/frontend/lib/components/Menu/components/HomeButton/HomeButton.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/components/Menu/components/HomeButton/HomeButton.tsx b/frontend/lib/components/Menu/components/HomeButton/HomeButton.tsx deleted file mode 100644 index 7ba59aecd..000000000 --- a/frontend/lib/components/Menu/components/HomeButton/HomeButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useTranslation } from "react-i18next"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; - -export const HomeButton = (): JSX.Element => { - const pathname = usePathname() ?? ""; - const isSelected = pathname.includes("/search"); - const { t } = useTranslation("chat"); - - return ( - - - - ); -}; diff --git a/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss b/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss deleted file mode 100644 index 9daee1d13..000000000 --- a/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss +++ /dev/null @@ -1,44 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.menu_button_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - padding: Spacings.$spacing03; - border-radius: Radius.$normal; - overflow: hidden; - border-left: 2px solid transparent; - - &.selected { - border-left: 2px solid var(--primary-0); - border-radius: 0 Radius.$normal Radius.$normal 0; - } - - .left { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - overflow: hidden; - - .title { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - color: var(--text-3); - - &.gold { - color: var(--gold); - } - - &.primary { - color: var(--primary-0); - } - } - } - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/lib/components/Menu/components/MenuButton/MenuButton.tsx b/frontend/lib/components/Menu/components/MenuButton/MenuButton.tsx deleted file mode 100644 index 53ee82872..000000000 --- a/frontend/lib/components/Menu/components/MenuButton/MenuButton.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { capitalCase } from "change-case"; -import { useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { iconList } from "@/lib/helpers/iconList"; - -import styles from "./MenuButton.module.scss"; - -export interface ButtonProps { - onClick?: () => void; - label: string; - iconName: keyof typeof iconList; - type: "add" | "open"; - isSelected?: boolean; - color: "gold" | "primary"; - parentHovered?: boolean; -} - -export const MenuButton = (props: ButtonProps): JSX.Element => { - const [isHovered, setIsHovered] = useState(false); - - return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > -
- - - {capitalCase(props.label)} - -
-
- ); -}; diff --git a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.module.scss b/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.module.scss deleted file mode 100644 index b889e2d88..000000000 --- a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.module.scss +++ /dev/null @@ -1,154 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.notification { - padding-inline: Spacings.$spacing05; - padding-block: Spacings.$spacing05; - border-bottom: 1px solid var(--border-2); - display: flex; - flex-direction: column; - - &:hover { - cursor: pointer; - background-color: var(--background-2); - } - - .header { - display: flex; - align-items: center; - justify-content: space-between; - padding-bottom: Spacings.$spacing03; - - .left { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - - .title { - font-size: Typography.$small; - color: var(--text-4); - } - - .brain_name { - display: flex; - align-items: center; - font-size: Typography.$small; - gap: Spacings.$spacing02; - font-weight: 500; - } - } - } - - .subtitle { - font-size: Typography.$tiny; - font-style: italic; - } - - .loader_wrapper { - padding-block: Spacings.$spacing02; - display: flex; - width: 100%; - align-items: center; - - .left { - width: 100px; - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .count { - display: flex; - font-size: Typography.$very_tiny; - font-weight: 600; - - &.success { - color: var(--success); - } - - &.warning { - color: var(--warning); - } - } - } - } - - .status_report { - display: flex; - gap: Spacings.$spacing06; - - .success { - display: flex; - align-items: center; - color: var(--success); - gap: Spacings.$spacing02; - font-weight: 500; - border-radius: Radius.$normal; - padding-inline: Spacings.$spacing03; - padding-block: Spacings.$spacing02; - margin-left: -(Spacings.$spacing03); - - &:hover { - background-color: var(--background-success); - } - } - - .error { - display: flex; - align-items: center; - color: var(--dangerous); - gap: Spacings.$spacing02; - font-weight: 500; - border-radius: Radius.$normal; - padding-inline: Spacings.$spacing03; - padding-block: Spacings.$spacing02; - margin-left: -(Spacings.$spacing03); - - &:hover { - background-color: var(--dangerous-lightest); - } - } - } - - .errors_header { - display: flex; - align-items: center; - color: var(--dangerous); - - .title { - font-size: Typography.$very_tiny; - } - - &:hover { - cursor: pointer; - color: var(--dangerous-dark); - } - } - - .file_list { - display: flex; - flex-direction: column; - gap: Spacings.$spacing02; - padding-top: Spacings.$spacing02; - - .title { - font-size: Typography.$very_tiny; - - &.error { - color: var(--dangerous); - } - - &.success { - color: var(--success); - } - } - } - - .date_time { - display: flex; - width: 100%; - font-size: Typography.$very_tiny; - justify-content: flex-end; - color: var(--text-4); - } -} diff --git a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.tsx b/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.tsx deleted file mode 100644 index a1dffe51a..000000000 --- a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/FeedingNotification.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { formatDistanceToNow } from "date-fns"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; - -import { useBrainFetcher } from "@/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; -import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; -import { Brain } from "@/lib/context/BrainProvider/types"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -import styles from "./FeedingNotification.module.scss"; -import { NotificationLoadingBar } from "./NotificationLoadingBar/NotificationLoadingBar"; - -import { BulkNotification, NotificationType } from "../../../types/types"; - -interface FeedingNotificationProps { - bulkNotification: BulkNotification; -} - -const NotificationHeader = ({ - bulkNotification, - brain, - onDelete, - allNotifsProcessed, -}: { - bulkNotification: BulkNotification; - brain?: Brain; - onDelete: () => void; - allNotifsProcessed: boolean; -}) => ( -
-
- - - {bulkNotification.category === "upload" && - `${allNotifsProcessed ? "Uploaded" : "Uploading"} files `} - {bulkNotification.category === "crawl" && - `${allNotifsProcessed ? "Crawled" : "Crawling"} websites `} - {bulkNotification.category === "sync" && - `${allNotifsProcessed ? "Synced" : "Syncing"} files `} - - for - - {brain && ( -
- - {brain.name} -
- )} -
- {bulkNotification.notifications.every( - (notif) => notif.status !== "info" - ) && ( -
{ - event.stopPropagation(); - onDelete(); - }} - > - -
- )} -
-); - -const NotificationIcon = ({ - notifications, -}: { - notifications: NotificationType[]; -}) => { - const hasPending = notifications.some((notif) => notif.status === "info"); - const allSuccess = notifications.every((notif) => notif.status === "success"); - - if (hasPending) { - return ; - } - if (allSuccess) { - return ; - } - - return ; -}; - -const NotificationCount = ({ - notifications, -}: { - notifications: NotificationType[]; -}) => { - const total = notifications.length; - const completed = notifications.filter( - (notif) => notif.status !== "info" - ).length; - const hasError = notifications.some((notif) => notif.status === "error"); - - let className = ""; - if (completed === total) { - className = hasError ? styles.warning : styles.success; - } - - return ( -
- {`${completed} / ${total}`} -
- ); -}; - -export const FeedingNotification = ({ - bulkNotification, -}: FeedingNotificationProps): JSX.Element => { - const { brain } = useBrainFetcher({ brainId: bulkNotification.brain_id }); - const { supabase } = useSupabase(); - const { updateNotifications } = useNotificationsContext(); - const [errorsOpened, setErrorsOpened] = useState(false); - const [successOpened, setSuccessOpened] = useState(false); - const router = useRouter(); - - const navigateToBrain = () => { - router.push(`/studio/${bulkNotification.brain_id}`); - }; - - const allNotifsProcessed = bulkNotification.notifications.every( - (notif) => notif.status !== "info" - ); - - const deleteNotification = async () => { - await supabase - .from("notifications") - .delete() - .match({ bulk_id: bulkNotification.bulk_id }); - - await updateNotifications(); - }; - - return ( -
navigateToBrain()}> - void deleteNotification()} - allNotifsProcessed={allNotifsProcessed} - /> - {bulkNotification.notifications.some( - (notif) => notif.status === "info" - ) ? ( -
-
-
- -
- -
- -
- ) : ( -
- {bulkNotification.notifications.some( - (notif) => notif.status === "success" - ) && ( -
{ - event.stopPropagation(); - setSuccessOpened(!successOpened); - setErrorsOpened(false); - }} - > - - - { - bulkNotification.notifications.filter( - (notif) => notif.status === "success" - ).length - } - -
- )} - {bulkNotification.notifications.some( - (notif) => notif.status === "error" - ) && ( -
{ - event.stopPropagation(); - setErrorsOpened(!errorsOpened); - setSuccessOpened(false); - }} - > - - - { - bulkNotification.notifications.filter( - (notif) => notif.status === "error" - ).length - } - -
- )} -
- )} - {errorsOpened && ( -
- {bulkNotification.notifications - .filter((notif) => notif.status === "error") - .map((notif, index) => ( - - - {notif.title} - - - ))} -
- )} - {successOpened && ( -
- {bulkNotification.notifications - .filter((notif) => notif.status === "success") - .map((notif, index) => ( - - - {notif.title} - - - ))} -
- )} -
- {formatDistanceToNow(new Date(bulkNotification.datetime), { - addSuffix: true, - }).replace("about ", "")} -
-
- ); -}; - -export default FeedingNotification; diff --git a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.module.scss b/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.module.scss deleted file mode 100644 index f760fdf13..000000000 --- a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "styles/Radius.module.scss"; - -.loading_bar { - overflow: hidden; - display: flex; - width: 100%; - - .bar_section { - height: 4px; - - &.info { - background-color: var(--background-3); - } - - &.success { - background-color: var(--success); - } - - &.error { - background-color: var(--dangerous-dark); - } - } -} diff --git a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.tsx b/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.tsx deleted file mode 100644 index 4997637d7..000000000 --- a/frontend/lib/components/Menu/components/Notifications/FeedingNotification/NotificationLoadingBar/NotificationLoadingBar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { BulkNotification } from "@/lib/components/Menu/types/types"; - -import styles from "./NotificationLoadingBar.module.scss"; - -interface NotificationProps { - bulkNotification: BulkNotification; -} - -interface StatusCounts { - [status: string]: number; -} - -export const NotificationLoadingBar = ({ - bulkNotification, -}: NotificationProps): JSX.Element => { - const statusCounts = bulkNotification.notifications.reduce( - (acc: StatusCounts, notification) => { - const { status } = notification; - acc[status] = (acc[status] ?? 0) + 1; - - return acc; - }, - {} as StatusCounts - ); - - const totalCount = bulkNotification.notifications.length; - - const statusOrder = ["success", "error", "warning", "info"]; - const sortedStatusCounts = Object.entries(statusCounts).sort( - (a, b) => statusOrder.indexOf(a[0]) - statusOrder.indexOf(b[0]) - ); - - return ( -
- {sortedStatusCounts.map(([status, count]) => ( -
- ))} -
- ); -}; - -export default NotificationLoadingBar; diff --git a/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.module.scss b/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.module.scss deleted file mode 100644 index f1e63f434..000000000 --- a/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.module.scss +++ /dev/null @@ -1,44 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.notification { - padding-inline: Spacings.$spacing05; - padding-block: Spacings.$spacing05; - border-bottom: 1px solid var(--border-2); - display: flex; - flex-direction: column; - - &:hover { - cursor: pointer; - background-color: var(--background-2); - } - - .header { - display: flex; - justify-content: space-between; - align-items: center; - - .brain_name { - display: flex; - align-items: center; - font-size: Typography.$small; - gap: Spacings.$spacing02; - font-weight: 500; - padding-bottom: Spacings.$spacing03; - } - } - - .content { - padding-bottom: Spacings.$spacing03; - font-size: Typography.$tiny; - } - - .date_time { - display: flex; - width: 100%; - font-size: Typography.$very_tiny; - justify-content: flex-end; - color: var(--text-4); - } -} diff --git a/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.tsx b/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.tsx deleted file mode 100644 index 2ae06385a..000000000 --- a/frontend/lib/components/Menu/components/Notifications/GenericNotification/GenericNotification.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { formatDistanceToNow } from "date-fns"; -import { useRouter } from "next/navigation"; - -import { useBrainFetcher } from "@/app/studio/[brainId]/BrainManagementTabs/hooks/useBrainFetcher"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -import styles from "./GenericNotification.module.scss"; - -import { BulkNotification } from "../../../types/types"; - -interface GenericNotificationProps { - bulkNotification: BulkNotification; -} - -export const GenericNotification = ({ - bulkNotification, -}: GenericNotificationProps): JSX.Element => { - const { brain } = useBrainFetcher({ brainId: bulkNotification.brain_id }); - const router = useRouter(); - const { supabase } = useSupabase(); - const { updateNotifications } = useNotificationsContext(); - - const navigateToBrain = () => { - router.push(`/studio/${bulkNotification.brain_id}`); // Naviguer vers l'URL - }; - - const deleteNotification = async () => { - await supabase - .from("notifications") - .delete() - .match({ bulk_id: bulkNotification.bulk_id }); - - await updateNotifications(); - }; - - return ( -
navigateToBrain()}> -
- {brain && ( -
- - {brain.name} -
- )} -
{ - event.stopPropagation(); - void deleteNotification(); - }} - > - -
-
- - {bulkNotification.notifications[0].description} - -
- {formatDistanceToNow(new Date(bulkNotification.datetime), { - addSuffix: true, - }).replace("about ", "")} -
-
- ); -}; - -export default GenericNotification; diff --git a/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss b/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss deleted file mode 100644 index c1a1c39ac..000000000 --- a/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss +++ /dev/null @@ -1,64 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.notifications_wrapper { - position: relative; - z-index: ZIndexes.$overlay; - - .notifications_panel_header { - display: flex; - align-items: center; - justify-content: space-between; - font-size: Typography.$small; - padding: Spacings.$spacing05; - background-color: var(--background-2); - border-bottom: 1px solid var(--border-2); - - &:hover { - cursor: pointer; - - .left { - .icon { - visibility: visible; - } - } - } - - .left { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - - .title { - color: var(--text-2); - font-size: Typography.$small; - font-weight: 500; - } - - .icon { - visibility: hidden; - } - } - - .buttons { - display: flex; - gap: Spacings.$spacing02; - } - } - - .no_notifications { - padding: Spacings.$spacing05; - font-size: Typography.$tiny; - color: var(--text-2); - } - - .feeding { - color: var(--text-2); - font-size: Typography.$small; - font-weight: 500; - padding-left: Spacings.$spacing05; - } -} diff --git a/frontend/lib/components/Menu/components/Notifications/Notifications.tsx b/frontend/lib/components/Menu/components/Notifications/Notifications.tsx deleted file mode 100644 index 8870c2a51..000000000 --- a/frontend/lib/components/Menu/components/Notifications/Notifications.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { TextButton } from "@/lib/components/ui/TextButton/TextButton"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import { FeedingNotification } from "./FeedingNotification/FeedingNotification"; -import { GenericNotification } from "./GenericNotification/GenericNotification"; -import styles from "./Notifications.module.scss"; - -export const Notifications = (): JSX.Element => { - const { bulkNotifications, updateNotifications, setIsVisible } = - useNotificationsContext(); - const { isMobile } = useDevice(); - const { supabase } = useSupabase(); - const [genericNotificationsDisplayed, setGenericNotificationsDisplayed] = - useState(true); - const [feedingNotificationsDisplayed, setFeedingNotificationsDisplayed] = - useState(true); - - const deleteAllNotifications = async ( - notificationType: "generic" | "feeding" - ) => { - if (notificationType === "generic") { - await supabase.from("notifications").delete().match({ - category: "generic", - }); - } else { - await supabase - .from("notifications") - .delete() - .not("category", "eq", "generic"); - } - - await updateNotifications(); - }; - - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as Node; - const panel = document.getElementById("notifications-panel"); - const button = document.getElementById("notifications-button"); - - if (!panel || !button) { - return; - } - - if (!panel.contains(target) && !button.contains(target)) { - setIsVisible(false); - } - }; - - useEffect(() => { - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - const hasGenericNotifications = () => { - return bulkNotifications.some((notif) => notif.category === "generic"); - }; - - const renderGenericNotifications = () => { - return ( - <> - {bulkNotifications.map((notification, i) => - notification.category === "generic" ? ( - - ) : null - )} - - ); - }; - - const renderFeedingNotifications = () => { - return ( - <> -
- setFeedingNotificationsDisplayed(!feedingNotificationsDisplayed) - } - > -
- {isMobile && !hasGenericNotifications() && ( - setIsVisible(false)} - /> - )} - Knowledge feeding in progress -
- -
-
-
- void deleteAllNotifications("feeding")} - small={true} - /> -
-
- - {feedingNotificationsDisplayed && - bulkNotifications.map((notification, i) => - notification.category !== "generic" ? ( - - ) : null - )} - - ); - }; - - return ( -
-
- {(bulkNotifications.length === 0 || hasGenericNotifications()) && ( -
- setGenericNotificationsDisplayed(!genericNotificationsDisplayed) - } - > -
- {isMobile && ( - setIsVisible(false)} - /> - )} - Notifications -
- -
-
-
- void deleteAllNotifications("generic")} - small={true} - /> -
-
- )} - - {bulkNotifications.length === 0 && ( -
- You have no notifications -
- )} - - {hasGenericNotifications() && - genericNotificationsDisplayed && - renderGenericNotifications()} - - {bulkNotifications.some((notif) => notif.category !== "generic") && - renderFeedingNotifications()} -
-
- ); -}; - -export default Notifications; diff --git a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss deleted file mode 100644 index 62aa8716b..000000000 --- a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.button_wrapper { - display: flex; - gap: Spacings.$spacing02; - align-items: center; - border-radius: Radius.$normal; - justify-content: space-between; - padding-right: Spacings.$spacing03; - cursor: pointer; - - .badge { - color: var(--white-0); - background-color: var(--dangerous-dark); - border-radius: Radius.$normal; - width: 18px; - height: 18px; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$very_tiny; - top: -(Spacings.$spacing03); - right: -(Spacings.$spacing02); - } - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx deleted file mode 100644 index 72a946b95..000000000 --- a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect } from "react"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; -import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -import styles from "./NotificationsButton.module.scss"; - -export const NotificationsButton = (): JSX.Element => { - const { isVisible, setIsVisible, unreadNotifications, updateNotifications } = - useNotificationsContext(); - const { supabase } = useSupabase(); - - useEffect(() => { - const channel = supabase - .channel("notifications") - .on( - "postgres_changes", - { event: "*", schema: "public", table: "notifications" }, - () => { - void updateNotifications(); - } - ) - .subscribe(); - - return () => { - void supabase.removeChannel(channel); - }; - }, []); - - useEffect(() => { - void updateNotifications(); - }, []); - - return ( -
{ - setIsVisible(!isVisible); - event.preventDefault(); - event.nativeEvent.stopImmediatePropagation(); - }} - id="notifications-button" - > - - {!!unreadNotifications && ( - - {unreadNotifications > 9 ? "9+" : unreadNotifications} - - )} -
- ); -}; diff --git a/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.module.scss b/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.module.scss deleted file mode 100644 index 74d16754e..000000000 --- a/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.button_wrapper { - display: flex; - gap: Spacings.$spacing02; - align-items: center; - border-radius: Radius.$normal; - - .credits { - display: flex; - align-items: center; - gap: Spacings.$spacing02; - - .number { - font-size: Typography.$tiny; - color: var(--gold); - } - } - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.tsx b/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.tsx deleted file mode 100644 index 7c418d14d..000000000 --- a/frontend/lib/components/Menu/components/ProfileButton/ProfileButton.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; - -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useUserData } from "@/lib/hooks/useUserData"; - -import styles from "./ProfileButton.module.scss"; - -const showTokens = process.env.NEXT_PUBLIC_SHOW_TOKENS === "true"; - -export const ProfileButton = (): JSX.Element => { - const [isHovered, setIsHovered] = useState(false); - const pathname = usePathname() ?? ""; - const isSelected = pathname.includes("/user"); - const { userIdentityData } = useUserData(); - const { getUserCredits } = useUserApi(); - const { remainingCredits, setRemainingCredits } = useUserSettingsContext(); - - let username = userIdentityData?.username ?? "Profile"; - - useEffect(() => { - username = userIdentityData?.username ?? "Profile"; - }, [userIdentityData]); - - useEffect(() => { - void (async () => { - const res = await getUserCredits(); - setRemainingCredits(res); - })(); - }, []); - - return ( - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > - - {remainingCredits !== null && !!showTokens && ( -
- {remainingCredits} - -
- )} - - ); -}; diff --git a/frontend/lib/components/Menu/components/QualityAssistantButton/QualityAssistantButton.module.scss b/frontend/lib/components/Menu/components/QualityAssistantButton/QualityAssistantButton.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/components/Menu/components/QualityAssistantButton/QualityAssistantButton.tsx b/frontend/lib/components/Menu/components/QualityAssistantButton/QualityAssistantButton.tsx deleted file mode 100644 index 45a58f005..000000000 --- a/frontend/lib/components/Menu/components/QualityAssistantButton/QualityAssistantButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; - -export const QualityAssistantButton = (): JSX.Element => { - const pathname = usePathname() ?? ""; - const isSelected = pathname.includes("/quality-assistant"); - - return ( - - - - ); -}; diff --git a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss deleted file mode 100644 index 12c709e40..000000000 --- a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.socials_buttons_wrapper { - display: flex; - gap: Spacings.$spacing05; - justify-content: space-between; - padding-inline: Spacings.$spacing06; - - .left { - display: flex; - gap: Spacings.$spacing05; - justify-content: center; - } -} diff --git a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx deleted file mode 100644 index 0b486a04a..000000000 --- a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import styles from "./SocialsButtons.module.scss"; - -export const SocialsButtons = (): JSX.Element => { - const { isDarkMode, setIsDarkMode } = useUserSettingsContext(); - const [lightModeIconName, setLightModeIconName] = useState("sun"); - - const toggleTheme = () => { - setIsDarkMode(!isDarkMode); - }; - - useEffect(() => { - setLightModeIconName(isDarkMode ? "sun" : "moon"); - }, [isDarkMode]); - - const handleClick = (url: string) => { - window.open(url, "_blank"); - }; - - return ( -
-
- handleClick("https://github.com/QuivrHQ/quivr")} - /> - - handleClick("https://www.linkedin.com/company/getquivr") - } - /> - handleClick("https://twitter.com/quivr_brain")} - /> - handleClick("https://discord.gg/HUpRgp2HG8")} - /> -
- -
- ); -}; diff --git a/frontend/lib/components/Menu/components/StudioButton/StudioButton.module.scss b/frontend/lib/components/Menu/components/StudioButton/StudioButton.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/components/Menu/components/StudioButton/StudioButton.tsx b/frontend/lib/components/Menu/components/StudioButton/StudioButton.tsx deleted file mode 100644 index da42558a3..000000000 --- a/frontend/lib/components/Menu/components/StudioButton/StudioButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; - -export const StudioButton = (): JSX.Element => { - const pathname = usePathname(); - const isSelected = pathname ? pathname.includes("/studio") : false; - - return ( - - - - ); -}; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss deleted file mode 100644 index db10e78c2..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.history_content_wrapper { - position: relative; - padding: Spacings.$spacing03; - padding-top: 0; - color: var(--text-2); - font-size: Typography.$small; - max-height: 300px; - overflow-y: auto; - display: flex; - flex-direction: column; -} diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.tsx b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.tsx deleted file mode 100644 index 0530283ae..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { isToday } from "date-fns"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { FoldableSection } from "@/lib/components/ui/FoldableSection/FoldableSection"; -import { useChatsContext } from "@/lib/context/ChatsProvider/hooks/useChatsContext"; - -import styles from "./ThreadsButton.module.scss"; -import { ThreadsSection } from "./ThreadsSection/ThreadsSection"; -import { isWithinLast30Days, isWithinLast7Days, isYesterday } from "./utils"; - -export const ThreadsButton = (): JSX.Element => { - const [canScrollDown, setCanScrollDown] = useState(false); - const { allChats } = useChatsContext(); - const { t } = useTranslation("chat"); - const todayChats = allChats.filter((chat) => - isToday(new Date(chat.creation_time)) - ); - const yesterdayChats = allChats.filter((chat) => - isYesterday(new Date(chat.creation_time)) - ); - const last7DaysChats = allChats.filter((chat) => - isWithinLast7Days(new Date(chat.creation_time)) - ); - const last30DaysChats = allChats.filter((chat) => - isWithinLast30Days(new Date(chat.creation_time)) - ); - - useEffect(() => { - const wrapper = document.querySelector( - `.${styles.history_content_wrapper}` - ); - - setCanScrollDown(!!wrapper && wrapper.clientHeight >= 200); - - const handleScroll = () => { - if (wrapper) { - const maxScrollTop = wrapper.scrollHeight - wrapper.clientHeight; - setCanScrollDown( - wrapper.scrollTop < maxScrollTop && wrapper.clientHeight >= 200 - ); - } - }; - - wrapper?.addEventListener("scroll", handleScroll); - - return () => wrapper?.removeEventListener("scroll", handleScroll); - }, [allChats]); - - return ( - -
- - - - -
-
- ); -}; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss deleted file mode 100644 index 342688dcc..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss +++ /dev/null @@ -1,54 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.thread_item_wrapper { - color: var(--text-3); - display: flex; - justify-content: space-between; - gap: Spacings.$spacing03; - align-items: center; - overflow: hidden; - - .edit_thread_name { - @include Typography.EllipsisOverflow; - - color: var(--text-3); - border: none; - height: 21px; - outline: none; - padding: 0; - font-size: Typography.$small; - background-color: var(--background-3); - border-radius: Radius.$small; - - &:focus { - box-shadow: none; - } - } - - .thread_item_name { - @include Typography.EllipsisOverflow; - - &:hover { - color: var(--primary-0); - } - } - - .icon_wrapper { - visibility: hidden; - } - - .options_modal { - position: absolute; - right: Spacings.$spacing02; - z-index: ZIndexes.$modal; - } - - &:hover { - .icon_wrapper { - visibility: visible; - } - } -} diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.tsx b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.tsx deleted file mode 100644 index 72e30ac1a..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import Link from "next/link"; -import { useEffect, useRef, useState } from "react"; - -import { ChatEntity } from "@/app/chat/[chatId]/types"; -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { OptionsModal } from "@/lib/components/ui/OptionsModal/OptionsModal"; -import { Option } from "@/lib/types/Options"; - -import styles from "./ThreadItem.module.scss"; - -import { useChatsListItem } from "../../hooks/useChatsListItem"; - -type ChatHistoryItemProps = { - chatHistoryItem: ChatEntity; -}; - -export const ThreadItem = ({ - chatHistoryItem, -}: ChatHistoryItemProps): JSX.Element => { - const [optionsOpened, setOptionsOpened] = useState(false); - - const { - chatName, - deleteChat, - editingName, - handleEditNameClick, - setChatName, - } = useChatsListItem(chatHistoryItem); - - const onNameEdited = () => { - handleEditNameClick(); - }; - - const optionsRef = useRef(null); - const iconRef = useRef(null); - - const options: Option[] = [ - { - label: "Edit", - onClick: () => onNameEdited(), - iconName: "edit", - iconColor: "primary", - }, - { - label: "Delete", - onClick: () => void deleteChat(), - iconName: "delete", - iconColor: "dangerous", - }, - ]; - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - iconRef.current && - !iconRef.current.contains(event.target as Node) && - optionsRef.current && - !optionsRef.current.contains(event.target as Node) - ) { - setOptionsOpened(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - return ( - <> -
- {editingName ? ( - setChatName(event.target.value)} - value={chatName} - onKeyDown={(event) => { - if (event.key === "Enter") { - onNameEdited(); - } - }} - autoFocus - /> - ) : ( - - {chatName.trim()} - - )} -
) => { - event.nativeEvent.stopImmediatePropagation(); - if (editingName) { - onNameEdited(); - } else { - setOptionsOpened(!optionsOpened); - } - }} - > -
- -
-
- {optionsOpened && } -
-
-
- - ); -}; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss deleted file mode 100644 index 275db4074..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.chats_wrapper { - border-left: 1px solid var(--border-2); - padding-left: Spacings.$spacing03; - margin: Spacings.$spacing02; - display: flex; - flex-direction: column; - gap: calc(#{Spacings.$spacing01} + #{Spacings.$spacing02}); - position: relative; -} diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.tsx b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.tsx deleted file mode 100644 index c097faebd..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ChatEntity } from "@/app/chat/[chatId]/types"; - -import { ThreadItem } from "./ThreadItem/ThreadItem"; -import styles from "./ThreadsSection.module.scss"; - -type ChatSectionProps = { - chats: ChatEntity[]; - title: string; -}; - -export const ThreadsSection = (props: ChatSectionProps): JSX.Element => { - if (props.chats.length === 0) { - return <>; - } - - return ( -
-
{props.title}
-
- {props.chats.map((chat) => ( - - ))} -
-
- ); -}; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/hooks/useChatsListItem.ts b/frontend/lib/components/Menu/components/ThreadsButton/hooks/useChatsListItem.ts deleted file mode 100644 index 6ac83b630..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/hooks/useChatsListItem.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { usePathname, useRouter } from "next/navigation"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { ChatEntity } from "@/app/chat/[chatId]/types"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useChatsContext } from "@/lib/context/ChatsProvider/hooks/useChatsContext"; -import { useToast } from "@/lib/hooks"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useChatsListItem = (chat: ChatEntity) => { - const pathname = usePathname()?.split("/").at(-1); - const selected = chat.chat_id === pathname; - const [chatName, setChatName] = useState(chat.chat_name); - const { publish } = useToast(); - const [editingName, setEditingName] = useState(false); - const { updateChat, deleteChat } = useChatApi(); - const { setAllChats } = useChatsContext(); - const router = useRouter(); - const { t } = useTranslation(["chat"]); - - const deleteChatHandler = async () => { - const chatId = chat.chat_id; - try { - await deleteChat(chatId); - setAllChats((chats) => - chats.filter((currentChat) => currentChat.chat_id !== chatId) - ); - // TODO: Change route only when the current chat is being deleted - void router.push("/search"); - publish({ - text: t("chatDeleted", { id: chatId, ns: "chat" }), - variant: "success", - }); - } catch (error) { - console.error(t("errorDeleting", { error: error, ns: "chat" })); - publish({ - text: t("errorDeleting", { error: error, ns: "chat" }), - variant: "danger", - }); - } - }; - - const updateChatName = async () => { - if (chatName !== chat.chat_name) { - await updateChat(chat.chat_id, { chat_name: chatName }); - publish({ - text: t("chatNameUpdated", { ns: "chat" }), - variant: "success", - }); - } - }; - - const handleEditNameClick = () => { - if (editingName) { - setEditingName(false); - void updateChatName(); - } else { - setEditingName(true); - } - }; - - return { - setChatName, - editingName, - chatName, - selected, - handleEditNameClick, - deleteChat: deleteChatHandler, - }; -}; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/utils.ts b/frontend/lib/components/Menu/components/ThreadsButton/utils.ts deleted file mode 100644 index 4e6653ae0..000000000 --- a/frontend/lib/components/Menu/components/ThreadsButton/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const isToday = (date: Date): boolean => { - const today = new Date(); - - return date.toDateString() === today.toDateString(); -}; - -export const isYesterday = (date: Date): boolean => { - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - - return date.toDateString() === yesterday.toDateString(); -}; - -export const isWithinLast7Days = (date: Date): boolean => { - const weekAgo = new Date(); - weekAgo.setDate(weekAgo.getDate() - 7); - - return date > weekAgo && !isToday(date) && !isYesterday(date); -}; - -export const isWithinLast30Days = (date: Date): boolean => { - const monthAgo = new Date(); - monthAgo.setDate(monthAgo.getDate() - 30); - - return ( - date > monthAgo && - !isToday(date) && - !isYesterday(date) && - !isWithinLast7Days(date) - ); -}; diff --git a/frontend/lib/components/Menu/components/UpgradeToPlusButton/UpgradeToPlusButton.module.scss b/frontend/lib/components/Menu/components/UpgradeToPlusButton/UpgradeToPlusButton.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/lib/components/Menu/components/UpgradeToPlusButton/UpgradeToPlusButton.tsx b/frontend/lib/components/Menu/components/UpgradeToPlusButton/UpgradeToPlusButton.tsx deleted file mode 100644 index 821e9d9b3..000000000 --- a/frontend/lib/components/Menu/components/UpgradeToPlusButton/UpgradeToPlusButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; -import { StripePricingModal } from "@/lib/components/Stripe"; -import { useUserData } from "@/lib/hooks/useUserData"; - -export const UpgradeToPlusButton = (): JSX.Element => { - const { userData } = useUserData(); - const is_premium = userData?.is_premium; - const { t } = useTranslation("monetization"); - - if (is_premium === true) { - return <>; - } - - return ( - - } - user_email={userData?.email ?? ""} - /> - ); -}; diff --git a/frontend/lib/components/Menu/hooks/useOutsideClickListener.ts b/frontend/lib/components/Menu/hooks/useOutsideClickListener.ts deleted file mode 100644 index cb808912b..000000000 --- a/frontend/lib/components/Menu/hooks/useOutsideClickListener.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { useDevice } from "@/lib/hooks/useDevice"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useOutsideClickListener = () => { - const { isOpened, setIsOpened } = useMenuContext(); - const { isMobile } = useDevice(); - - const onClickOutside = () => { - if (isOpened && isMobile) { - setIsOpened(false); - } - }; - - return { - onClickOutside, - }; -}; diff --git a/frontend/lib/components/Menu/types/types.ts b/frontend/lib/components/Menu/types/types.ts deleted file mode 100644 index 4c7219977..000000000 --- a/frontend/lib/components/Menu/types/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { UUID } from "crypto"; - -export enum NotificationStatus { - Info = "info", - Warning = "warning", - Error = "error", - Success = "success", -} - -export interface BulkNotification { - notifications: NotificationType[]; - bulk_id: string; - category: "sync" | "upload" | "crawl" | "generic"; - brain_id: UUID; - datetime: string; -} - -export interface NotificationType { - id: string; - title: string; - datetime: string; - status: NotificationStatus; - archived: boolean; - read: boolean; - description: string; - bulk_id: string; - category: "sync" | "upload" | "crawl" | "generic"; - brain_id: UUID; -} diff --git a/frontend/lib/components/OnboardingModal/OnboardingModal.module.scss b/frontend/lib/components/OnboardingModal/OnboardingModal.module.scss deleted file mode 100644 index c8d16f976..000000000 --- a/frontend/lib/components/OnboardingModal/OnboardingModal.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.modal_content_wrapper { - height: 100%; - padding-block: Spacings.$spacing05; - display: flex; - flex-direction: column; - justify-content: space-between; - - .form_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing05; - } - - .button_wrapper { - width: 100%; - display: flex; - justify-content: flex-end; - } -} diff --git a/frontend/lib/components/OnboardingModal/OnboardingModal.tsx b/frontend/lib/components/OnboardingModal/OnboardingModal.tsx deleted file mode 100644 index f335bea50..000000000 --- a/frontend/lib/components/OnboardingModal/OnboardingModal.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { Controller, FormProvider, useForm } from "react-hook-form"; - -import { useUserApi } from "@/lib/api/user/useUserApi"; -import { CompanySize, UsagePurpose } from "@/lib/api/user/user"; -import { Modal } from "@/lib/components/ui/Modal/Modal"; -import { useOnboardingContext } from "@/lib/context/OnboardingProvider/hooks/useOnboardingContext"; - -import styles from "./OnboardingModal.module.scss"; - -import { OnboardingProps } from "../OnboardingModal/types/types"; -import { FieldHeader } from "../ui/FieldHeader/FieldHeader"; -import { QuivrButton } from "../ui/QuivrButton/QuivrButton"; -import { SingleSelector } from "../ui/SingleSelector/SingleSelector"; -import { TextInput } from "../ui/TextInput/TextInput"; - -export const OnboardingModal = (): JSX.Element => { - const { - isOnboardingModalOpened, - setIsOnboardingModalOpened, - isBrainCreated, - } = useOnboardingContext(); - - const methods = useForm({ - defaultValues: { - username: "", - companyName: "", - companySize: undefined, - usagePurpose: "", - }, - }); - const { watch } = methods; - const username = watch("username"); - - const { updateUserIdentity } = useUserApi(); - - const companySizeOptions = Object.entries(CompanySize).map(([, value]) => ({ - label: value, - value: value, - })); - - const usagePurposeOptions = Object.entries(UsagePurpose).map( - ([key, value]) => ({ - label: value, - value: key, - }) - ); - - const submitForm = async () => { - await updateUserIdentity({ - username: methods.getValues("username"), - company: methods.getValues("companyName"), - onboarded: isBrainCreated, - company_size: methods.getValues("companySize"), - usage_purpose: methods.getValues("usagePurpose") as - | UsagePurpose - | undefined, - }); - window.location.reload(); - }; - - return ( - - } - unclosable={true} - > -
-
-
- - ( - - )} - /> -
-
- - ( - - )} - /> -
-
- - ( - - )} - /> -
-
- - ( - - )} - /> -
-
-
- submitForm()} - disabled={!username} - /> -
-
-
-
- ); -}; diff --git a/frontend/lib/components/OnboardingModal/types/types.ts b/frontend/lib/components/OnboardingModal/types/types.ts deleted file mode 100644 index 995825ba3..000000000 --- a/frontend/lib/components/OnboardingModal/types/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { CompanySize } from "@/lib/api/user/user"; - -export type OnboardingProps = { - username: string; - companyName: string; - companySize: CompanySize; - usagePurpose: string; -}; diff --git a/frontend/lib/components/PageHeader/PageHeader.module.scss b/frontend/lib/components/PageHeader/PageHeader.module.scss deleted file mode 100644 index 10e7cd119..000000000 --- a/frontend/lib/components/PageHeader/PageHeader.module.scss +++ /dev/null @@ -1,68 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/Variables.module.scss"; - -.page_header_wrapper { - display: flex; - align-items: center; - justify-content: space-between; - padding: Spacings.$spacing04; - padding-inline: Spacings.$spacing09; - border-bottom: 1px solid var(--border-1); - height: Variables.$pageHeaderHeight; - width: 100%; - - .left { - @include Typography.H2; - display: flex; - align-items: center; - gap: Spacings.$spacing03; - visibility: visible; - - &.menu_closed { - visibility: visible; - padding-left: Spacings.$spacing07; - } - - .brain_snippet { - width: 24px; - height: 24px; - border-radius: Radius.$normal; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$tiny; - } - } - - .buttons_wrapper { - display: flex; - gap: Spacings.$spacing04; - align-items: center; - justify-content: flex-end; - } - - @media (max-width: ScreenSizes.$small) { - justify-content: flex-end; - .left { - display: none; - } - } -} - -.help_button { - display: flex; - padding: Spacings.$spacing02; - position: absolute; - top: calc(Spacings.$spacing03 + Spacings.$spacing01); - right: Spacings.$spacing03; - border-radius: Radius.$circle; - cursor: pointer; - background-color: var(--background-2); - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/lib/components/PageHeader/PageHeader.tsx b/frontend/lib/components/PageHeader/PageHeader.tsx deleted file mode 100644 index aa79ad7a9..000000000 --- a/frontend/lib/components/PageHeader/PageHeader.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext"; -import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { ButtonType } from "@/lib/types/QuivrButton"; - -import styles from "./PageHeader.module.scss"; - -import { Icon } from "../ui/Icon/Icon"; -import { QuivrButton } from "../ui/QuivrButton/QuivrButton"; - -type Props = { - iconName: string; - label: string; - buttons: ButtonType[]; - snippetEmoji?: string; - snippetColor?: string; -}; - -export const PageHeader = ({ - iconName, - label, - buttons, - snippetColor, - snippetEmoji, -}: Props): JSX.Element => { - const { isOpened } = useMenuContext(); - const { isVisible, setIsVisible } = useHelpContext(); - - return ( - <> -
-
- {snippetEmoji && snippetColor ? ( -
- {snippetEmoji} -
- ) : ( - - )} - {label} -
-
- {buttons.map((button, index) => ( -
-
-
setIsVisible(!isVisible)} - > - -
- - ); -}; - -export default PageHeader; diff --git a/frontend/lib/components/SearchModal/SearchModal.module.scss b/frontend/lib/components/SearchModal/SearchModal.module.scss deleted file mode 100644 index 5b3eac58e..000000000 --- a/frontend/lib/components/SearchModal/SearchModal.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.search_modal_wrapper { - display: flex; - background-color: var(--background-blur); - height: 100%; - width: 100%; - position: absolute; - z-index: ZIndexes.$modal; - align-items: center; - justify-content: center; - - .search_bar_wrapper { - width: 50%; - - @media (max-width: ScreenSizes.$small) { - width: 100%; - margin-inline: Spacings.$spacing07; - } - } -} diff --git a/frontend/lib/components/SearchModal/SearchModal.tsx b/frontend/lib/components/SearchModal/SearchModal.tsx deleted file mode 100644 index 290df748d..000000000 --- a/frontend/lib/components/SearchModal/SearchModal.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useEffect, useRef } from "react"; - -import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; - -import styles from "./SearchModal.module.scss"; - -import { SearchBar } from "../ui/SearchBar/SearchBar"; - -export const SearchModal = (): JSX.Element => { - const { isVisible, setIsVisible } = useSearchModalContext(); - const searchBarRef = useRef(null); - - const keydownHandler = ({ - key, - metaKey, - }: { - key: string; - metaKey: boolean; - }) => { - if (metaKey && key === "k") { - setIsVisible(true); - } else if (key === "Escape") { - setIsVisible(false); - } - }; - - const clickHandler = (event: MouseEvent) => { - if ( - !(searchBarRef.current as HTMLElement | null)?.contains( - event.target as Node - ) - ) { - setIsVisible(false); - } - }; - - const handleSearch = () => { - setIsVisible(false); - }; - - useEffect(() => { - document.addEventListener("keydown", keydownHandler); - document.addEventListener("click", clickHandler); - - return () => { - document.removeEventListener("keydown", keydownHandler); - document.removeEventListener("click", clickHandler); - }; - }, []); - - if (!isVisible) { - return <>; - } - - return ( -
-
- -
-
- ); -}; - -export default SearchModal; diff --git a/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx b/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx deleted file mode 100644 index 7d2926525..000000000 --- a/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { StripePricingTable } from "./components/PricingTable/PricingTable"; - -import { Modal } from "../../ui/Modal/Modal"; - -type StripePricingModalProps = { - Trigger: JSX.Element; - user_email: string; -}; - -export const StripePricingModal = ({ - Trigger, - user_email, -}: StripePricingModalProps): JSX.Element => { - return ( - } unforceWhite={true}> - - - ); -}; diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss deleted file mode 100644 index e2bc40cb6..000000000 --- a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.info_content { - padding: Spacings.$spacing06; - - .bold { - font-weight: 800; - } -} diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx deleted file mode 100644 index 6aafa4f85..000000000 --- a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; - -import styles from "./PricingTable.module.scss"; - -const PRICING_TABLE_ID = process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID; -const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; - -export const StripePricingTable = ({ - user_email, -}: { - user_email: string; -}): JSX.Element => { - return ( - <> -
- -
- {"The free tier allows you to have"} - 3 brains - {"and"} - 25 chat credits - { - "per month. You can upgrade to unlock more brains, more chat credits and access to premium models." - } -
-
-
-
- - -
- - ); -}; diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/Types.ts b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/Types.ts deleted file mode 100644 index 8d511e89e..000000000 --- a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/Types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from "react"; - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace JSX { - interface IntrinsicElements { - "stripe-pricing-table": React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - >; - } - } -} diff --git a/frontend/lib/components/Stripe/index.ts b/frontend/lib/components/Stripe/index.ts deleted file mode 100644 index 3c8adb1df..000000000 --- a/frontend/lib/components/Stripe/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./PricingModal/StripePricingModal"; diff --git a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss b/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss deleted file mode 100644 index 2ba506a7b..000000000 --- a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "styles/Spacings.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.knowledge_modal { - position: relative; - display: flex; - flex-direction: column; - justify-content: space-between; - background-color: var(--background-0); - width: 100%; - flex: 1; - overflow: hidden; - - .buttons { - display: flex; - justify-content: space-between; - - &.standalone { - justify-content: flex-end; - } - } -} diff --git a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.tsx b/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.tsx deleted file mode 100644 index 1236ed442..000000000 --- a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components"; -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { OpenedConnection } from "@/lib/api/sync/types"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { createHandleGetButtonProps } from "@/lib/helpers/handleConnectionButtons"; - -import styles from "./UploadDocumentModal.module.scss"; -import { useAddKnowledge } from "./hooks/useAddKnowledge"; - -import { Modal } from "../ui/Modal/Modal"; -import { QuivrButton } from "../ui/QuivrButton/QuivrButton"; - -export const UploadDocumentModal = (): JSX.Element => { - const { shouldDisplayFeedCard, setShouldDisplayFeedCard, knowledgeToFeed } = - useKnowledgeToFeedContext(); - const { currentBrain } = useBrainContext(); - const { feedBrain } = useAddKnowledge(); - const [feeding, setFeeding] = useState(false); - const { - currentSyncId, - setCurrentSyncId, - openedConnections, - setOpenedConnections, - } = useFromConnectionsContext(); - const [currentConnection, setCurrentConnection] = useState< - OpenedConnection | undefined - >(undefined); - - useKnowledgeToFeedContext(); - const { t } = useTranslation(["knowledge"]); - - const disabled = useMemo(() => { - return ( - (knowledgeToFeed.length === 0 && - openedConnections.filter((connection) => { - return connection.submitted || !!connection.last_synced; - }).length === 0) || - !currentBrain - ); - }, [knowledgeToFeed, openedConnections, currentBrain, currentSyncId]); - - const handleFeedBrain = async () => { - setFeeding(true); - await feedBrain(); - setFeeding(false); - setShouldDisplayFeedCard(false); - }; - - const getButtonProps = createHandleGetButtonProps( - currentConnection, - openedConnections, - setOpenedConnections, - currentSyncId, - setCurrentSyncId - ); - const buttonProps = getButtonProps(); - - useEffect(() => { - setCurrentConnection( - openedConnections.find( - (connection) => connection.user_sync_id === currentSyncId - ) - ); - }, [currentSyncId]); - - if (!shouldDisplayFeedCard) { - return <>; - } - - return ( - } - > -
- -
- {!!currentSyncId && ( - { - setCurrentSyncId(undefined); - }} - /> - )} - {currentSyncId ? ( - - ) : ( - { - setOpenedConnections([]); - void handleFeedBrain(); - }} - disabled={disabled} - isLoading={feeding} - important={true} - /> - )} -
-
-
- ); -}; diff --git a/frontend/lib/components/UploadDocumentModal/hooks/useAddKnowledge.ts b/frontend/lib/components/UploadDocumentModal/hooks/useAddKnowledge.ts deleted file mode 100644 index 2c2ff3b7f..000000000 --- a/frontend/lib/components/UploadDocumentModal/hooks/useAddKnowledge.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from "react"; - -import { useKnowledge } from "@/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/hooks/useKnowledge"; -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; - -import { useFeedBrain } from "./useFeedBrain"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useAddKnowledge = () => { - const { brainId } = useUrlBrain(); - const { invalidateKnowledgeDataKey } = useKnowledge({ - brainId, - }); - - const { feedBrain, hasPendingRequests, setHasPendingRequests } = useFeedBrain( - { - dispatchHasPendingRequests: () => setHasPendingRequests(true), - } - ); - - useEffect(() => { - if (!hasPendingRequests) { - invalidateKnowledgeDataKey(); - } - }, [hasPendingRequests, invalidateKnowledgeDataKey]); - - return { - feedBrain, - hasPendingRequests, - }; -}; diff --git a/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrain.ts b/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrain.ts deleted file mode 100644 index d3d442eb2..000000000 --- a/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrain.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; -import { useToast } from "@/lib/hooks"; -import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl"; - -import { useFeedBrainHandler } from "./useFeedBrainHandler"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useFeedBrain = ({ - dispatchHasPendingRequests, - closeFeedInput, -}: { - dispatchHasPendingRequests?: () => void; - closeFeedInput?: () => void; -}) => { - const { publish } = useToast(); - const { t } = useTranslation(["upload"]); - let { brainId } = useUrlBrain(); - const { currentBrainId } = useBrainContext(); - const { setKnowledgeToFeed, knowledgeToFeed, setShouldDisplayFeedCard } = - useKnowledgeToFeedContext(); - const [hasPendingRequests, setHasPendingRequests] = useState(false); - const { handleFeedBrain } = useFeedBrainHandler(); - const { openedConnections } = useFromConnectionsContext(); - - const { createChat, deleteChat } = useChatApi(); - - const feedBrain = async (): Promise => { - brainId ??= currentBrainId ?? undefined; - if (brainId === undefined) { - publish({ - variant: "danger", - text: t("selectBrainFirst"), - }); - - return; - } - - if (knowledgeToFeed.length === 0 && !openedConnections.length) { - publish({ - variant: "danger", - text: t("addFiles"), - }); - - return; - } - - //TODO: Modify backend archi to avoid creating a chat for each feed action - const currentChatId = (await createChat("New Chat")).chat_id; - - try { - dispatchHasPendingRequests?.(); - closeFeedInput?.(); - setHasPendingRequests(true); - await handleFeedBrain({ - brainId, - chatId: currentChatId, - }); - setShouldDisplayFeedCard(false); - setKnowledgeToFeed([]); - } catch (e) { - publish({ - variant: "danger", - text: JSON.stringify(e), - }); - } finally { - setHasPendingRequests(false); - await deleteChat(currentChatId); - } - }; - - return { - feedBrain, - hasPendingRequests, - setHasPendingRequests, - }; -}; diff --git a/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrainHandler.ts b/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrainHandler.ts deleted file mode 100644 index 914a2df0e..000000000 --- a/frontend/lib/components/UploadDocumentModal/hooks/useFeedBrainHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { UUID } from "crypto"; - -import { useFromConnectionsContext } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromConnections/FromConnectionsProvider/hooks/useFromConnectionContext"; -import { useSync } from "@/lib/api/sync/useSync"; -import { useKnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts"; -import { useKnowledgeToFeedFilesAndUrls } from "@/lib/hooks/useKnowledgeToFeed"; -import { useOnboarding } from "@/lib/hooks/useOnboarding"; - -type FeedBrainProps = { - brainId: UUID; - chatId: UUID; -}; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useFeedBrainHandler = () => { - const { files, urls } = useKnowledgeToFeedFilesAndUrls(); - const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput(); - const { updateOnboarding, onboarding } = useOnboarding(); - const { - syncFiles, - getActiveSyncsForBrain, - deleteActiveSync, - updateActiveSync, - } = useSync(); - const { openedConnections } = useFromConnectionsContext(); - - const updateOnboardingA = async () => { - if (onboarding.onboarding_a) { - await updateOnboarding({ - onboarding_a: false, - }); - } - }; - - const handleFeedBrain = async ({ - brainId, - chatId, - }: FeedBrainProps): Promise => { - const uploadPromises = files.map((file) => - uploadFileHandler(file, brainId, chatId) - ); - const crawlPromises = urls.map((url) => - crawlWebsiteHandler(url, brainId, chatId) - ); - - const existingConnections = await getActiveSyncsForBrain(brainId); - - await Promise.all( - openedConnections - .filter((connection) => connection.selectedFiles.files.length) - .map(async (openedConnection) => { - const existingConnectionIds = existingConnections.map( - (connection) => connection.id - ); - if ( - !openedConnection.id || - !existingConnectionIds.includes(openedConnection.id) - ) { - await syncFiles(openedConnection, brainId); - } else if (!openedConnection.selectedFiles.files.length) { - await deleteActiveSync(openedConnection.id); - } else { - await updateActiveSync(openedConnection); - } - }) - ); - - await Promise.all([ - ...uploadPromises, - ...crawlPromises, - updateOnboardingA(), - ]); - }; - - return { - handleFeedBrain, - }; -}; diff --git a/frontend/lib/components/ui/BrainCard/BrainCard.module.scss b/frontend/lib/components/ui/BrainCard/BrainCard.module.scss deleted file mode 100644 index b77f0e14c..000000000 --- a/frontend/lib/components/ui/BrainCard/BrainCard.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.brain_card_container { - display: flex; - flex-direction: column; - gap: Spacings.$spacing02; - - &.disabled { - pointer-events: none; - opacity: 0.1; - } - - .brain_card_wrapper { - display: flex; - flex-direction: column; - align-items: center; - border-radius: Radius.$normal; - gap: Spacings.$spacing03; - padding: Spacings.$spacing04; - width: fit-content; - cursor: pointer; - width: 120px; - - .dark_image { - filter: invert(100%); - } - - .brain_title { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - font-weight: 500; - width: 100%; - display: flex; - justify-content: center; - } - - &:hover, - &.selected { - border-color: var(--primary-0); - background-color: var(--background-primary-0); - - .brain_title { - color: var(--primary-0); - } - } - } -} diff --git a/frontend/lib/components/ui/BrainCard/BrainCard.tsx b/frontend/lib/components/ui/BrainCard/BrainCard.tsx deleted file mode 100644 index 1632f43e0..000000000 --- a/frontend/lib/components/ui/BrainCard/BrainCard.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { capitalCase } from "change-case"; -import Image from "next/image"; - -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import styles from "./BrainCard.module.scss"; - -import { Tag } from "../Tag/Tag"; -import Tooltip from "../Tooltip/Tooltip"; - -interface BrainCardProps { - tooltip: string; - selected?: boolean; - imageUrl: string; - brainName: string; - tags: string[]; - callback: () => void; - cardKey: string; - disabled?: boolean; -} - -export const BrainCard = ({ - tooltip, - selected, - imageUrl, - brainName, - tags, - callback, - cardKey, - disabled, -}: BrainCardProps): JSX.Element => { - const { isDarkMode } = useUserSettingsContext(); - - return ( -
{ - callback(); - }} - > - -
- {brainName} - {brainName} -
- {tags[0] && } -
-
-
-
- ); -}; diff --git a/frontend/lib/components/ui/Button.tsx b/frontend/lib/components/ui/Button.tsx deleted file mode 100644 index acb95a0ab..000000000 --- a/frontend/lib/components/ui/Button.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-disable */ -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@radix-ui/react-tooltip"; -import { cva, type VariantProps } from "class-variance-authority"; -import { ButtonHTMLAttributes, Ref, RefAttributes, forwardRef } from "react"; - -import { cn } from "@/lib/utils"; -import { AiOutlineLoading3Quarters } from "react-icons/ai"; - -const ButtonVariants = cva( - "px-8 py-3 text-sm disabled:opacity-80 text-center font-medium rounded-md focus:ring ring-primary/10 outline-none flex items-center justify-center gap-2 transition-opacity focus:ring-0", - { - variants: { - variant: { - primary: - "bg-primary border border-black dark:border-white disabled:bg-gray-500 disabled:hover:bg-gray-500 text-white dark:bg-white dark:text-black hover:bg-gray-700 dark:hover:bg-gray-200 transition-colors", - tertiary: - "text-black dark:text-white bg-transparent py-2 px-4 disabled:opacity-25", - secondary: - "border border-black dark:border-white bg-white dark:bg-black text-black dark:text-white focus:bg-black dark:focus:bg-white hover:bg-black dark:hover:bg-white hover:text-white dark:hover:text-black focus:text-white dark:focus:text-black transition-colors shadow-none", - danger: - "border border-red-500 hover:bg-red-500 hover:text-white transition-colors", - }, - brightness: { - dim: "", - default: "", - }, - }, - defaultVariants: { - variant: "primary", - brightness: "default", - }, - } -); -export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps { - isLoading?: boolean; - tooltip?: string; -} - -const Button = forwardRef( - ( - { - className, - children, - variant, - brightness, - isLoading, - tooltip, - ...props - }: ButtonProps, - forwardedRef - ): JSX.Element => { - const buttonProps: ButtonProps & RefAttributes = { - className: cn(ButtonVariants({ variant, brightness, className })), - disabled: isLoading, - ...props, - ref: forwardedRef as Ref | undefined, - }; - - const buttonChildren = ( - <> - {children}{" "} - {isLoading && } - - ); - - if (tooltip !== undefined) { - return ( - - - {buttonChildren} - - {tooltip} - - - - ); - } - - return ; - } -); - -export default Button; diff --git a/frontend/lib/components/ui/Card.tsx b/frontend/lib/components/ui/Card.tsx deleted file mode 100644 index 8726b3e00..000000000 --- a/frontend/lib/components/ui/Card.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable */ -"use client"; -import { motion } from "framer-motion"; -import { forwardRef, HTMLAttributes, LegacyRef } from "react"; - -import { cn } from "@/lib/utils"; - -type CardProps = HTMLAttributes; - -const Card = forwardRef( - ({ children, className, ...props }: CardProps, ref): JSX.Element => { - return ( -
} - className={cn( - "shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl overflow-hidden bg-white dark:bg-black border border-black/10 dark:border-white/25", - className - )} - {...props} - > - {children} -
- ); - } -); - -type CardChildProps = HTMLAttributes; - -const CardHeader = ({ children, className }: CardChildProps) => { - return
{children}
; -}; -CardHeader.displayName = "CardHeader"; - -const CardBody = ({ children, className }: CardChildProps) => { - return
{children}
; -}; -CardBody.displayName = "CardHeader"; - -const CardFooter = ({ children, className }: CardChildProps) => { - return
{children}
; -}; -CardFooter.displayName = "CardHeader"; - -export { CardBody, CardFooter, CardHeader }; - -export const AnimatedCard = motion(Card); -AnimatedCard.displayName = "AnimatedCard"; -Card.displayName = "Card"; -export default Card; diff --git a/frontend/lib/components/ui/Checkbox/Checkbox.module.scss b/frontend/lib/components/ui/Checkbox/Checkbox.module.scss deleted file mode 100644 index 022043221..000000000 --- a/frontend/lib/components/ui/Checkbox/Checkbox.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; - -.checkbox_wrapper { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - cursor: pointer; - border-radius: Radius.$normal; - - .checkbox { - width: 16px; - height: 16px; - border: 1px solid var(--border-2); - display: flex; - justify-content: center; - align-items: center; - border-radius: Radius.$normal; - - &.filled { - background-color: var(--primary-0); - border-color: var(--primary-0); - } - } - - &.disabled { - background-color: var(--background-3); - opacity: 0.4; - cursor: default; - } - - &:hover { - background-color: var(--background-3); - } -} diff --git a/frontend/lib/components/ui/Checkbox/Checkbox.tsx b/frontend/lib/components/ui/Checkbox/Checkbox.tsx deleted file mode 100644 index 31f5e7d97..000000000 --- a/frontend/lib/components/ui/Checkbox/Checkbox.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useEffect, useState } from "react"; - -import styles from "./Checkbox.module.scss"; - -import { Icon } from "../Icon/Icon"; - -interface CheckboxProps { - label?: string; - checked: boolean; - setChecked: (value: boolean, event: React.MouseEvent) => void; - disabled?: boolean; -} - -export const Checkbox = ({ - label, - checked, - setChecked, - disabled, -}: CheckboxProps): JSX.Element => { - const [currentChecked, setCurrentChecked] = useState(checked); - - useEffect(() => { - setCurrentChecked(checked); - }, [checked]); - - return ( -
{ - event.stopPropagation(); - if (!disabled) { - setChecked(!currentChecked, event); - setCurrentChecked(!currentChecked); - } - }} - > -
- {currentChecked && } -
- {label && {label}} -
- ); -}; diff --git a/frontend/lib/components/ui/ColorSelector/ColorSelector.module.scss b/frontend/lib/components/ui/ColorSelector/ColorSelector.module.scss deleted file mode 100644 index 5034fe829..000000000 --- a/frontend/lib/components/ui/ColorSelector/ColorSelector.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; - -.color_picker_wrapper { - width: 375px; - height: 300px; - display: flex; - align-items: center; - justify-content: center; - - @media (max-width: ScreenSizes.$small) { - width: 250px; - height: 300px; - } -} diff --git a/frontend/lib/components/ui/ColorSelector/ColorSelector.tsx b/frontend/lib/components/ui/ColorSelector/ColorSelector.tsx deleted file mode 100644 index 381d6378e..000000000 --- a/frontend/lib/components/ui/ColorSelector/ColorSelector.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { HexColorPicker } from "react-colorful"; - -import styles from "./ColorSelector.module.scss"; - -export const ColorSelector = ({ - onSelectColor, - color, -}: { - onSelectColor?: (emoji: string) => void; - color: string; -}): JSX.Element => { - return ( -
- -
- ); -}; diff --git a/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.module.scss b/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.module.scss deleted file mode 100644 index 60c9928d2..000000000 --- a/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.connection_icon { - border-radius: Radius.$circle; - padding: Spacings.$spacing01; - color: var(--white-0); - min-width: 24px; - min-height: 24px; - max-height: 24px; - max-width: 24px; - font-size: Typography.$tiny; - display: flex; - align-items: center; - justify-content: center; - border: 2px solid var(--background-0); -} diff --git a/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.tsx b/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.tsx deleted file mode 100644 index f7ac5283a..000000000 --- a/frontend/lib/components/ui/ConnectionIcon/ConnectionIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styles from "./ConnectionIcon.module.scss"; - -interface ConnectionIconProps { - letter: string; - index: number; -} - -export const ConnectionIcon = ({ - letter, - index, -}: ConnectionIconProps): JSX.Element => { - const colors = ["#FBBC04", "#F28B82", "#8AB4F8", "#81C995", "#C58AF9"]; - - return ( -
- {letter.toUpperCase()} -
- ); -}; diff --git a/frontend/lib/components/ui/CopyButton.tsx b/frontend/lib/components/ui/CopyButton.tsx deleted file mode 100644 index 438a6e33b..000000000 --- a/frontend/lib/components/ui/CopyButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Icon } from "@/lib/components/ui/Icon/Icon"; -import { IconSize } from "@/lib/types/Icons"; - -type CopyButtonProps = { - handleCopy: () => void; - size: IconSize; -}; - -export const CopyButton = ({ - handleCopy, - size, -}: CopyButtonProps): JSX.Element => { - const [isCopied, setIsCopied] = useState(false); - - const handleClick = () => { - handleCopy(); - setIsCopied(true); - }; - - useEffect(() => { - if (isCopied) { - const timer = setTimeout(() => { - setIsCopied(false); - }, 2000); - - return () => { - clearTimeout(timer); - }; - } - }, [isCopied]); - - return ( - - ); -}; diff --git a/frontend/lib/components/ui/Divider.tsx b/frontend/lib/components/ui/Divider.tsx deleted file mode 100644 index e73f51c3f..000000000 --- a/frontend/lib/components/ui/Divider.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable */ -import { forwardRef, HTMLAttributes, LegacyRef } from "react"; - -import { cn } from "@/lib/utils"; - -type DividerProps = HTMLAttributes & { - text?: string; - textClassName?: string; - separatorClassName?: string; -}; - -const Divider = forwardRef( - ( - { - className, - separatorClassName, - textClassName, - text, - ...props - }: DividerProps, - ref - ): JSX.Element => { - return ( -
} - className={cn("flex items-center justify-center", className)} - {...props} - > -
- {text !== undefined && ( -

- {text} -

- )} -
-
- ); - } -); -Divider.displayName = "AnimatedCard"; - -export { Divider }; diff --git a/frontend/lib/components/ui/EmojiSelector/EmojiSelector.module.scss b/frontend/lib/components/ui/EmojiSelector/EmojiSelector.module.scss deleted file mode 100644 index 7af40e2e5..000000000 --- a/frontend/lib/components/ui/EmojiSelector/EmojiSelector.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.emoji_picker_wrapper { - ul { - li[data-name="suggested"] { - display: none; - } - } - - h2 { - @include Typography.H3; - color: var(--text-4); - } - - input { - &:focus { - border-color: var(--primary-0); - } - } - - aside { - border: none; - background-color: var(--background-0); - border-radius: 0; - } -} diff --git a/frontend/lib/components/ui/EmojiSelector/EmojiSelector.tsx b/frontend/lib/components/ui/EmojiSelector/EmojiSelector.tsx deleted file mode 100644 index bd4565334..000000000 --- a/frontend/lib/components/ui/EmojiSelector/EmojiSelector.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// components/EmojiSelector.tsx -import { EmojiClickData, Theme } from "emoji-picker-react"; -import dynamic from "next/dynamic"; - -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useDevice } from "@/lib/hooks/useDevice"; - -import styles from "./EmojiSelector.module.scss"; - -const EmojiPicker = dynamic(() => import("emoji-picker-react"), { ssr: false }); - -const EmojiSelector = ({ - onSelectEmoji, -}: { - onSelectEmoji?: (emoji: string) => void; -}): JSX.Element => { - const { isDarkMode } = useUserSettingsContext(); - const { isMobile } = useDevice(); - - const onEmojiClick = (emojiObject: EmojiClickData) => { - if (onSelectEmoji) { - onSelectEmoji(emojiObject.emoji); - } - }; - - return ( -
- -
- ); -}; - -export default EmojiSelector; diff --git a/frontend/lib/components/ui/Field.tsx b/frontend/lib/components/ui/Field.tsx deleted file mode 100644 index 468720dda..000000000 --- a/frontend/lib/components/ui/Field.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - DetailedHTMLProps, - forwardRef, - InputHTMLAttributes, - RefObject, -} from "react"; - -import { cn } from "@/lib/utils"; - -interface FieldProps - extends DetailedHTMLProps< - InputHTMLAttributes, - HTMLInputElement - > { - label?: string; - name: string; - icon?: React.ReactNode; - inputClassName?: string; -} - -const Field = forwardRef( - ( - { - label, - className, - name, - inputClassName, - required = false, - icon, - ...props - }: FieldProps, - forwardedRef - ) => { - return ( -
- {label !== undefined && ( - - )} -
- } - className={cn( - `w-full bg-gray-50 dark:bg-gray-900 px-4 py-2 border rounded-md border-black/10 dark:border-white/25`, - icon !== undefined ? "pr-12" : "", - inputClassName - )} - name={name} - id={name} - {...props} - /> - {icon !== undefined && ( -
- {icon} -
- )} -
-
- ); - } -); -Field.displayName = "Field"; - -export default Field; diff --git a/frontend/lib/components/ui/FieldHeader/FieldHeader.module.scss b/frontend/lib/components/ui/FieldHeader/FieldHeader.module.scss deleted file mode 100644 index e3c2d0f1b..000000000 --- a/frontend/lib/components/ui/FieldHeader/FieldHeader.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "styles/Colors.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.field_header_wrapper { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - padding-bottom: Spacings.$spacing02; - font-size: Typography.$small; - font-weight: 500; - - .mandatory { - color: Colors.$dangerous; - } -} diff --git a/frontend/lib/components/ui/FieldHeader/FieldHeader.tsx b/frontend/lib/components/ui/FieldHeader/FieldHeader.tsx deleted file mode 100644 index 10438dff0..000000000 --- a/frontend/lib/components/ui/FieldHeader/FieldHeader.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; - -import styles from "./FieldHeader.module.scss"; - -import { Icon } from "../Icon/Icon"; - -type FieldHeaderProps = { - iconName: string; - label: string; - help?: string; - mandatory?: boolean; -}; - -export const FieldHeader = ({ - iconName, - label, - help, - mandatory, -}: FieldHeaderProps): JSX.Element => { - return ( -
- - - {mandatory && *} - {help && "*"} - {help && ( - -
- -
-
- )} -
- ); -}; diff --git a/frontend/lib/components/ui/FileInput/FileInput.module.scss b/frontend/lib/components/ui/FileInput/FileInput.module.scss deleted file mode 100644 index 93d8a34b0..000000000 --- a/frontend/lib/components/ui/FileInput/FileInput.module.scss +++ /dev/null @@ -1,70 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.file_input_wrapper { - width: 100%; - height: 200px; - - &.drag_active { - .header_wrapper { - border: 3px dashed var(--accent); - background-color: var(--background-3); - } - } - - .header_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - border: 1px solid var(--border-2); - border-radius: Radius.$big; - padding: Spacings.$spacing03; - cursor: pointer; - height: 100%; - width: 100%; - - &:hover { - border: 3px dashed var(--accent); - } - - &.drag_active { - border: 3px dashed var(--accent); - background-color: var(--background-3); - } - - .box_content { - width: 100%; - display: flex; - flex-direction: column; - column-gap: Spacings.$spacing05; - justify-content: center; - align-items: center; - height: 100%; - - .input { - display: flex; - gap: Spacings.$spacing02; - padding: Spacings.$spacing05; - - @media (max-width: ScreenSizes.$small) { - flex-direction: column; - } - - .clickable { - font-weight: bold; - } - } - } - } -} - -.filename { - font-size: Typography.$tiny; -} - -.error_message { - font-size: Typography.$tiny; - color: var(--dangerous); -} diff --git a/frontend/lib/components/ui/FileInput/FileInput.tsx b/frontend/lib/components/ui/FileInput/FileInput.tsx deleted file mode 100644 index 69543b15a..000000000 --- a/frontend/lib/components/ui/FileInput/FileInput.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useRef, useState } from "react"; -import { Accept, useDropzone } from "react-dropzone"; - -import styles from "./FileInput.module.scss"; - -import { Icon } from "../Icon/Icon"; - -interface FileInputProps { - label: string; - onFileChange: (file: File) => void; - acceptedFileTypes?: string[]; -} - -export const FileInput = (props: FileInputProps): JSX.Element => { - const [currentFile, setCurrentFile] = useState(null); - const [errorMessage, setErrorMessage] = useState(""); - const fileInputRef = useRef(null); - - const handleFileChange = (file: File) => { - const fileExtension = file.name.split(".").pop(); - if (props.acceptedFileTypes?.includes(fileExtension || "")) { - props.onFileChange(file); - setCurrentFile(file); - setErrorMessage(""); - } else { - setErrorMessage("Wrong extension"); - } - }; - - const handleInputChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - handleFileChange(file); - } - }; - - const handleClick = () => { - fileInputRef.current?.click(); - }; - - const mimeTypes: { [key: string]: string } = { - pdf: "application/pdf", - doc: "application/msword", - docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - xls: "application/vnd.ms-excel", - xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - csv: "text/csv", - txt: "text/plain", - jpg: "image/jpeg", - jpeg: "image/jpeg", - png: "image/png", - }; - - const accept: Accept | undefined = props.acceptedFileTypes?.reduce( - (acc, type) => { - const mimeType = mimeTypes[type]; - if (mimeType) { - acc[mimeType] = []; - } - - return acc; - }, - {} as Accept - ); - - const { getRootProps, getInputProps, isDragActive } = useDropzone({ - onDrop: (acceptedFiles) => { - const file = acceptedFiles[0]; - if (file) { - handleFileChange(file); - } - }, - accept, - }); - - return ( -
-
-
- -
-
- Choose file -
- or drag it here -
- {currentFile && ( - {currentFile.name} - )} -
-
- - {errorMessage !== "" && ( - {errorMessage} - )} -
- ); -}; diff --git a/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss b/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss deleted file mode 100644 index baf2f8960..000000000 --- a/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss +++ /dev/null @@ -1,67 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Transitions.module.scss"; -@use "styles/Typography.module.scss"; - -.foldable_section_wrapper { - display: flex; - flex-direction: column; - border-radius: Radius.$normal; - border: 1px dashed var(--border-0); - overflow: hidden; - font-size: Typography.$small; - - .contentWrapper { - overflow: hidden; - transition: max-height 0.3s Transitions.$easeOutBack; - } - - .contentCollapsed { - max-height: 0; - } - - &.hide_border { - border-color: transparent; - } - - .header_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - padding: Spacings.$spacing03; - - .header_left { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - overflow: hidden; - - .header_title { - @include Typography.EllipsisOverflow; - } - } - - &:hover { - background-color: var(--background-3); - - .header_left { - .header_title { - color: var(--primary-0); - } - } - } - } - - .icon_rotate { - transition: transform 0.3s Transitions.$easeOutBack; - } - - .icon_rotate_down { - transform: rotate(0deg); - } - - .icon_rotate_right { - transform: rotate(90deg); - } -} diff --git a/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx b/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx deleted file mode 100644 index b6e9d27e1..000000000 --- a/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -import { iconList } from "@/lib/helpers/iconList"; - -import styles from "./FoldableSection.module.scss"; - -import { Icon } from "../Icon/Icon"; - -interface FoldableSectionProps { - label: string; - icon: keyof typeof iconList; - children: React.ReactNode; - foldedByDefault?: boolean; - hideBorder?: boolean; -} - -export const FoldableSection = (props: FoldableSectionProps): JSX.Element => { - const [folded, setFolded] = useState(false); - const contentRef = useRef(null); - - useEffect(() => { - setFolded(props.foldedByDefault ?? false); - }, [props.foldedByDefault]); - - const getContentHeight = (): string => { - return folded ? "0" : `${contentRef.current?.scrollHeight}px`; - }; - - return ( -
-
setFolded(!folded)}> -
- -

{props.label}

-
- -
-
- {props.children} -
-
- ); -}; diff --git a/frontend/lib/components/ui/Icon/Icon.module.scss b/frontend/lib/components/ui/Icon/Icon.module.scss deleted file mode 100644 index c0fccd57b..000000000 --- a/frontend/lib/components/ui/Icon/Icon.module.scss +++ /dev/null @@ -1,106 +0,0 @@ -@use "styles/IconSizes.module.scss"; - -.tiny { - min-width: IconSizes.$tiny; - min-height: IconSizes.$tiny; - max-width: IconSizes.$tiny; - max-height: IconSizes.$tiny; -} - -.small { - min-width: IconSizes.$small; - min-height: IconSizes.$small; - max-width: IconSizes.$small; - max-height: IconSizes.$small; -} - -.normal { - min-width: IconSizes.$normal; - min-height: IconSizes.$normal; - max-width: IconSizes.$normal; - max-height: IconSizes.$normal; -} - -.large { - min-width: IconSizes.$large; - min-height: IconSizes.$large; - max-width: IconSizes.$large; - max-height: IconSizes.$large; -} - -.big { - min-width: IconSizes.$big; - min-height: IconSizes.$big; - max-width: IconSizes.$big; - max-height: IconSizes.$big; -} - -.black { - color: var(--icon-3); - - &.hovered { - color: var(--primary-0); - } -} - -.dark-grey { - color: var(--icon-2); -} - -.grey { - color: var(--icon-1); -} - -.primary { - color: var(--primary-0); -} - -.accent { - color: var(--accent); -} - -.gold { - color: var(--gold); -} - -.white { - color: var(--icon-0); -} - -.static-white { - color: var(--white-0); - - &:hover { - color: var(--primary-0); - } -} - -.dangerous { - color: var(--dangerous); - - &.hovered { - color: var(--dangerous-dark); - } -} - -.dangerous-dark { - color: var(--dangerous-dark); -} - -.warning { - color: var(--warning); -} - -.success { - color: var(--success); -} - -.disabled { - color: var(--icon-3); - pointer-events: none; - opacity: 0.2; -} - -.hovered { - cursor: pointer; -} diff --git a/frontend/lib/components/ui/Icon/Icon.tsx b/frontend/lib/components/ui/Icon/Icon.tsx deleted file mode 100644 index 077b4822a..000000000 --- a/frontend/lib/components/ui/Icon/Icon.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useEffect, useState } from "react"; -import { IconType } from "react-icons/lib"; - -import { iconList } from "@/lib/helpers/iconList"; -import { Color } from "@/lib/types/Colors"; -import { IconSize } from "@/lib/types/Icons"; - -import styles from "./Icon.module.scss"; - -interface IconProps { - name: keyof typeof iconList; - size: IconSize; - color: Color; - disabled?: boolean; - classname?: string; - hovered?: boolean; - handleHover?: boolean; - onClick?: () => void | Promise; -} - -export const Icon = ({ - name, - size, - color, - disabled, - classname, - hovered, - handleHover, - onClick, -}: IconProps): JSX.Element => { - const [iconHovered, setIconHovered] = useState(false); - const IconComponent: IconType = iconList[name]; - - useEffect(() => { - if (!handleHover) { - setIconHovered(!!hovered); - } - }, [hovered, handleHover]); - - const handleMouseEnter = (event: React.MouseEvent) => { - if (handleHover) { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - setIconHovered(true); - } - }; - - const handleMouseLeave = () => { - if (handleHover) { - setIconHovered(false); - } - }; - - return ( - - ); -}; diff --git a/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.module.scss b/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.module.scss deleted file mode 100644 index 46cead10c..000000000 --- a/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -@use "styles/Spacings.module.scss"; - -.info_displayer_container { - display: flex; - flex-direction: column; -} diff --git a/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.tsx b/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.tsx deleted file mode 100644 index 5b3a73c58..000000000 --- a/frontend/lib/components/ui/InfoDisplayer/InfoDisplayer.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styles from "./InfoDisplayer.module.scss"; - -import { FieldHeader } from "../FieldHeader/FieldHeader"; - -type InfoDisplayerProps = { - iconName: string; - label: string; - children: React.ReactNode; -}; - -export const InfoDisplayer = ({ - iconName, - label, - children, -}: InfoDisplayerProps): JSX.Element => { - return ( -
- - {children} -
- ); -}; diff --git a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss b/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss deleted file mode 100644 index fcaccca08..000000000 --- a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "styles/IconSizes.module.scss"; - -.loader_icon { - animation: spin 1s linear infinite; - - @keyframes spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } - } -} diff --git a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.tsx b/frontend/lib/components/ui/LoaderIcon/LoaderIcon.tsx deleted file mode 100644 index 69485b5f6..000000000 --- a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Color } from "@/lib/types/Colors"; -import { IconSize } from "@/lib/types/Icons"; - -import styles from "./LoaderIcon.module.scss"; - -import { Icon } from "../Icon/Icon"; - -interface LoaderIconProps { - size: IconSize; - color: Color; -} - -export const LoaderIcon = (props: LoaderIconProps): JSX.Element => { - return ( - - ); -}; diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss deleted file mode 100644 index 1e8b65ee5..000000000 --- a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.message_info_box_wrapper { - padding: Spacings.$spacing03; - display: flex; - align-items: center; - gap: Spacings.$spacing03; - width: fit-content; - color: var(--text-3); - border-radius: Radius.$normal; - font-size: Typography.$small; - - &.success { - color: var(--success); - background-color: var(--success-lightest); - } - - &.info { - color: var(--primary-0); - background-color: var(--primary-2); - } - - &.warning { - color: var(--warning); - background-color: var(--warning-lightest); - } - - &.tutorial { - color: var(--gold); - background-color: var(--gold-lightest); - } - - &.dark { - background-color: var(--background-1); - } -} diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx deleted file mode 100644 index 0fb31030b..000000000 --- a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { iconList } from "@/lib/helpers/iconList"; -import { Color } from "@/lib/types/Colors"; - -import styles from "./MessageInfoBox.module.scss"; - -import { Icon } from "../Icon/Icon"; - -export type MessageInfoBoxProps = { - children: React.ReactNode; - type: "info" | "success" | "warning" | "error" | "tutorial"; - unforceWhite?: boolean; -}; - -export const MessageInfoBox = ({ - children, - type, - unforceWhite, -}: MessageInfoBoxProps): JSX.Element => { - const getIconProps = (): { - iconName: keyof typeof iconList; - iconColor: Color; - } => { - switch (type) { - case "info": - return { iconName: "info", iconColor: "primary" }; - case "success": - return { iconName: "check", iconColor: "success" }; - case "warning": - return { iconName: "warning", iconColor: "warning" }; - case "tutorial": - return { iconName: "step", iconColor: "gold" }; - default: - return { iconName: "info", iconColor: "primary" }; - } - }; - - const { isDarkMode } = useUserSettingsContext(); - - return ( -
- - {children} -
- ); -}; diff --git a/frontend/lib/components/ui/Modal/Modal.module.scss b/frontend/lib/components/ui/Modal/Modal.module.scss deleted file mode 100644 index 4a2825c29..000000000 --- a/frontend/lib/components/ui/Modal/Modal.module.scss +++ /dev/null @@ -1,69 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.modal_container { - display: flex; - background-color: var(--background-blur); - height: 100%; - width: 100%; - position: absolute; - z-index: ZIndexes.$modal; - align-items: center; - justify-content: center; - - .modal_content_wrapper { - display: flex; - flex-direction: column; - border-radius: Radius.$big; - background-color: var(--background-0); - padding-inline: Spacings.$spacing08; - padding-block: Spacings.$spacing06; - cursor: auto; - box-shadow: BoxShadow.$medium; - max-width: 90vw; - width: 35vw; - height: 80vh; - overflow: auto; - - .title { - @include Typography.H1; - } - - .subtitle { - font-size: Typography.$small; - } - - &.auto { - height: auto; - } - - &.big { - width: 50vw; - height: 95vh; - } - - &.white { - background-color: var(--white-0); - color: var(--text-0); - } - - @media (max-width: ScreenSizes.$small) { - min-width: 90vw; - } - - .close_button_wrapper { - display: inline-flex; - position: absolute; - top: 0; - right: 0; - padding: Spacings.$spacing06; - padding-inline: Spacings.$spacing08; - border-radius: 50; - outline: none; - } - } -} diff --git a/frontend/lib/components/ui/Modal/Modal.tsx b/frontend/lib/components/ui/Modal/Modal.tsx deleted file mode 100644 index 0c9a99f4b..000000000 --- a/frontend/lib/components/ui/Modal/Modal.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/*eslint max-lines: ["error", 200 ]*/ - -"use client"; -import * as Dialog from "@radix-ui/react-dialog"; -import { AnimatePresence, motion } from "framer-motion"; -import { ReactNode, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import styles from "./Modal.module.scss"; - -import Button from "../Button"; -import { Icon } from "../Icon/Icon"; - -type CommonModalProps = { - title?: string; - desc?: string; - children?: ReactNode; - Trigger?: ReactNode; - CloseTrigger?: ReactNode; - isOpen?: undefined; - setOpen?: undefined; - size?: "auto" | "normal" | "big"; - unclosable?: boolean; - unforceWhite?: boolean; -}; - -type ModalProps = - | CommonModalProps - | (Omit & { - isOpen: boolean; - setOpen: (isOpen: boolean) => void; - }); - -const handleInteractOutside = (unclosable: boolean, event: Event) => { - if (unclosable) { - event.preventDefault(); - } -}; - -const handleModalContentAnimation = ( - size: "auto" | "normal" | "big", - unforceWhite: boolean -) => { - const initialAnimation = { opacity: 0, y: "-40%" }; - const animateAnimation = { opacity: 1, y: "0%" }; - const exitAnimation = { opacity: 0, y: "40%" }; - - return { - initial: initialAnimation, - animate: animateAnimation, - exit: exitAnimation, - className: `${styles.modal_content_wrapper} ${styles[size]} ${ - unforceWhite ? styles.white : "" - }`, - }; -}; - -export const Modal = ({ - title, - desc, - children, - Trigger, - CloseTrigger, - isOpen: customIsOpen, - setOpen: customSetOpen, - size = "normal", - unclosable, - unforceWhite, -}: ModalProps): JSX.Element => { - const [isOpen, setOpen] = useState(false); - const { t } = useTranslation(["translation"]); - - return ( - - {Trigger !== undefined && ( - {Trigger} - )} - - {customIsOpen ?? isOpen ? ( - - - - - handleInteractOutside(!!unclosable, event) - } - > - - - {title} - - - {desc} - - {children} - - {CloseTrigger !== undefined ? ( - CloseTrigger - ) : ( - - )} - - {!unclosable && ( - - - - )} - - - - - - ) : null} - - - ); -}; diff --git a/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss b/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss deleted file mode 100644 index 25e2e1ac5..000000000 --- a/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.options_modal_wrapper { - background-color: var(--background-1); - border-radius: Radius.$normal; - border-radius: Radius.$normal; - box-shadow: BoxShadow.$small; - width: fit-content; - - .option { - padding: Spacings.$spacing02; - padding-inline: Spacings.$spacing05; - display: flex; - gap: Spacings.$spacing05; - align-items: center; - cursor: pointer; - justify-content: space-between; - overflow: hidden; - font-size: Typography.$small; - border: 1px solid transparent; - border-radius: Radius.$normal; - - &:hover { - border-color: var(--primary-0); - color: var(--primary-0); - } - - &.disabled { - cursor: not-allowed; - pointer-events: none; - opacity: 0.5; - } - } -} diff --git a/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx b/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx deleted file mode 100644 index ed0b1221e..000000000 --- a/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -import { Option } from "@/lib/types/Options"; - -import styles from "./OptionsModal.module.scss"; - -import { Icon } from "../Icon/Icon"; - -type OptionsModalProps = { - options: Option[]; -}; - -export const OptionsModal = ({ options }: OptionsModalProps): JSX.Element => { - const [hovered, setHovered] = useState( - new Array(options.length).fill(false) - ); - - const handleMouseEnter = (index: number) => { - setHovered((prevHovered) => - prevHovered.map((h, i) => (i === index ? true : h)) - ); - }; - - const handleMouseLeave = (index: number) => { - setHovered((prevHovered) => - prevHovered.map((h, i) => (i === index ? false : h)) - ); - }; - - const modalRef = useRef(null); - - useEffect(() => { - modalRef.current?.focus(); - }, []); - - return ( -
- {options.map((option, index) => ( -
{ - event.preventDefault(); - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - option.onClick(); - }} - onMouseEnter={() => handleMouseEnter(index)} - onMouseLeave={() => handleMouseLeave(index)} - > - {option.label} - -
- ))} -
- ); -}; diff --git a/frontend/lib/components/ui/PageHeading.tsx b/frontend/lib/components/ui/PageHeading.tsx deleted file mode 100644 index 0850a4e85..000000000 --- a/frontend/lib/components/ui/PageHeading.tsx +++ /dev/null @@ -1,27 +0,0 @@ -interface PageHeadingProps { - title: string; - subtitle?: string; -} - -const PageHeading = ({ title, subtitle }: PageHeadingProps): JSX.Element => { - return ( -
-

- {title} -

- {subtitle !== undefined && ( -

- {subtitle} -

- )} -
- ); -}; - -export default PageHeading; diff --git a/frontend/lib/components/ui/Popover.tsx b/frontend/lib/components/ui/Popover.tsx deleted file mode 100644 index e3151bb2b..000000000 --- a/frontend/lib/components/ui/Popover.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as PopoverPrimitive from "@radix-ui/react-popover"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverContent, PopoverTrigger }; diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss deleted file mode 100644 index 17f938639..000000000 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss +++ /dev/null @@ -1,110 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.button_wrapper { - font-size: Typography.$small; - white-space: nowrap; - padding-inline: Spacings.$spacing04; - padding-block: Spacings.$spacing02; - border-radius: Radius.$normal; - border: 1.5px solid transparent; - cursor: pointer; - display: flex; - width: fit-content; - background-color: var(--background-0); - height: fit-content; - - &.important { - text-transform: uppercase; - } - - &.dark { - background-color: var(--background-1); - - &:hover, - &.important { - .label { - color: var(--white-0); - } - } - } - - &.hidden { - display: none; - } - - @media (max-width: ScreenSizes.$small) { - padding-inline: Spacings.$spacing03; - padding-block: Spacings.$spacing01; - font-size: Typography.$tiny; - } - - &.small { - padding-inline: Spacings.$spacing03; - padding-block: Spacings.$spacing01; - font-size: Typography.$tiny; - } - - &.disabled { - border-color: var(--border-2); - cursor: default; - color: var(--text-1); - background-color: var(--background-0); - - &.dark { - opacity: 0.2; - } - } - - &.primary:not(.disabled) { - border-color: var(--primary-0); - color: var(--primary-0); - - &:hover, - &.important { - background-color: var(--primary-0); - color: var(--text-0); - } - } - - &.dangerous:not(.disabled) { - border-color: var(--dangerous); - color: var(--dangerous); - - &:hover, - &.important { - background-color: var(--dangerous); - color: var(--text-0); - } - } - - &.gold:not(.disabled) { - border-color: var(--gold); - color: var(--gold); - - &:hover, - &.important { - background-color: var(--gold); - color: var(--text-0); - } - } - - .icon_label { - display: flex; - flex-direction: row; - gap: Spacings.$spacing02; - align-items: center; - - @media (max-width: ScreenSizes.$small) { - .icon { - display: none; - } - - .label { - font-size: Typography.$very_tiny; - } - } - } -} diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx deleted file mode 100644 index c1e1fd9f6..000000000 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useState } from "react"; - -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { ButtonType } from "@/lib/types/QuivrButton"; - -import styles from "./QuivrButton.module.scss"; - -import { Icon } from "../Icon/Icon"; -import { LoaderIcon } from "../LoaderIcon/LoaderIcon"; -import Tooltip from "../Tooltip/Tooltip"; - -export const QuivrButton = ({ - onClick, - label, - color, - isLoading, - iconName, - disabled, - hidden, - important, - small, - tooltip, -}: ButtonType): JSX.Element => { - const [hovered, setHovered] = useState(false); - const { isDarkMode } = useUserSettingsContext(); - - const handleMouseEnter = () => setHovered(true); - const handleMouseLeave = () => setHovered(false); - - const handleClick = (event: React.MouseEvent) => { - event.nativeEvent.stopImmediatePropagation(); - if (!disabled) { - void onClick?.(); - } - }; - - const useIconColor = () => { - if ((hovered && !disabled) || (important && !disabled)) { - return isDarkMode ? "black" : "white"; - } - if (disabled) { - return "grey"; - } - - return color; - }; - - const iconColor = useIconColor(); - - const buttonClasses = `${styles.button_wrapper} ${styles[color]} ${ - isDarkMode ? styles.dark : "" - } ${hidden ? styles.hidden : ""} ${important ? styles.important : ""} ${ - disabled ? styles.disabled : "" - } ${small ? styles.small : ""}`; - - const ButtonContent = ( -
-
- {!isLoading ? ( - - ) : ( - - )} - {label} -
-
- ); - - return disabled ? ( - {ButtonContent} - ) : ( - ButtonContent - ); -}; - -export default QuivrButton; diff --git a/frontend/lib/components/ui/Radio.tsx b/frontend/lib/components/ui/Radio.tsx deleted file mode 100644 index 44a2c0c34..000000000 --- a/frontend/lib/components/ui/Radio.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { forwardRef } from "react"; - -import { cn } from "@/lib/utils"; - -type RadioItem = { - value: string; - label: string; -}; - -interface RadioProps { - name: string; - items: RadioItem[]; - label?: string; - className?: string; -} - -export const Radio = forwardRef( - ({ items, label, className, ...props }, ref) => ( -
- {label !== undefined && ( - - )} -
- {items.map((item) => ( -
- - -
- ))} -
-
- ) -); - -Radio.displayName = "Radio"; diff --git a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss b/frontend/lib/components/ui/SearchBar/SearchBar.module.scss deleted file mode 100644 index 55540df67..000000000 --- a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss +++ /dev/null @@ -1,49 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/IconSizes.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; - -.search_bar_wrapper { - display: flex; - flex-direction: column; - gap: Spacings.$spacing03; - background-color: var(--background-0); - border-radius: Radius.$big; - border: 1px solid var(--border-0); - overflow: auto; - box-shadow: BoxShadow.$large; - - &.new_brain { - border-color: var(--primary-0); - } - - .editor_wrapper { - display: flex; - padding: Spacings.$spacing05; - gap: Spacings.$spacing05; - - &.disabled { - pointer-events: none; - opacity: 0.3; - padding-top: 0; - } - - &.current { - padding-top: 0; - } - - .search_icon { - width: IconSizes.$big; - height: 100%; - color: var(--accent); - cursor: pointer; - align-self: flex-end; - - &.disabled { - color: var(--text-3); - pointer-events: none; - opacity: 0.2; - } - } - } -} diff --git a/frontend/lib/components/ui/SearchBar/SearchBar.tsx b/frontend/lib/components/ui/SearchBar/SearchBar.tsx deleted file mode 100644 index 6162655a4..000000000 --- a/frontend/lib/components/ui/SearchBar/SearchBar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useEffect, useState } from "react"; -import { LuSearch } from "react-icons/lu"; - -import { Editor } from "@/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/Editor"; -import { useChatInput } from "@/app/chat/[chatId]/components/ActionsBar/components/ChatInput/hooks/useChatInput"; -import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; -import { useChatContext } from "@/lib/context"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; - -import styles from "./SearchBar.module.scss"; - -import { CurrentBrain } from "../../CurrentBrain/CurrentBrain"; -import { LoaderIcon } from "../LoaderIcon/LoaderIcon"; - -export const SearchBar = ({ - onSearch, - newBrain, -}: { - onSearch?: () => void; - newBrain?: boolean; -}): JSX.Element => { - const [searching, setSearching] = useState(false); - const [isDisabled, setIsDisabled] = useState(true); - const [placeholder, setPlaceholder] = useState("Select a @brain"); - const { message, setMessage } = useChatInput(); - const { setMessages } = useChatContext(); - const { addQuestion } = useChat(); - const { currentBrain, setCurrentBrainId } = useBrainContext(); - const { remainingCredits } = useUserSettingsContext(); - - useEffect(() => { - setCurrentBrainId(null); - }, []); - - useEffect(() => { - setIsDisabled(message === ""); - }, [message]); - - useEffect(() => { - setPlaceholder(currentBrain ? "Ask a question..." : "Select a @brain"); - }, [currentBrain]); - - const submit = async (): Promise => { - if (!!remainingCredits && !!currentBrain && !searching) { - setSearching(true); - setMessages([]); - try { - if (onSearch) { - onSearch(); - } - await addQuestion(message); - } catch (error) { - console.error(error); - } finally { - setSearching(false); - } - } - }; - - return ( -
- -
- void submit()} - placeholder={placeholder} - > - {searching ? ( - - ) : ( - void submit()} - /> - )} -
-
- ); -}; diff --git a/frontend/lib/components/ui/Select.tsx b/frontend/lib/components/ui/Select.tsx deleted file mode 100644 index c5f4d9e08..000000000 --- a/frontend/lib/components/ui/Select.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/*eslint complexity: ["error", 10]*/ - -/* eslint-disable max-lines */ -import { BsCheckCircleFill } from "react-icons/bs"; - -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/lib/components/ui/Popover"; -import { cn } from "@/lib/utils"; - -export type SelectOptionProps = { - label: string; - value: T; -}; - -type SelectProps = { - options: SelectOptionProps[]; - value?: T; - onChange: (option: T) => void; - label?: string; - readOnly?: boolean; - className?: string; - emptyLabel?: string; - popoverClassName?: string; - popoverSide?: "top" | "bottom" | "left" | "right" | undefined; -}; - -const selectedStyle = "rounded-lg bg-black text-white"; - -export const Select = ({ - onChange, - options, - value, - label, - readOnly = false, - className, - emptyLabel, - popoverClassName, - popoverSide, -}: SelectProps): JSX.Element => { - const selectedValueLabel = options.find( - (option) => option.value === value - )?.label; - - if (readOnly) { - return ( -
- {label !== undefined && ( - - )} -
- -
-
- ); - } - - return ( -
- {label !== undefined && ( - - )} -
- - - - - -
    - {options.map((option) => ( -
  • onChange(option.value)} - > -
    - - {option.label} - - {value === option.value && } -
    -
  • - ))} -
-
-
-
-
- ); -}; diff --git a/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss b/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss deleted file mode 100644 index 0d2fe38dc..000000000 --- a/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss +++ /dev/null @@ -1,122 +0,0 @@ -@use "styles/IconSizes.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.single_selector_wrapper { - display: flex; - flex-direction: column; - position: relative; - font-size: Typography.$small; - - .first_line_wrapper { - display: flex; - justify-content: space-between; - border: 1px solid var(--border-2); - border-radius: Radius.$big; - align-items: center; - background-color: var(--background-1); - cursor: pointer; - display: flex; - - &.unfolded { - border-radius: Radius.$big Radius.$big 0 0; - } - - .left { - display: flex; - align-items: center; - gap: Spacings.$spacing03; - padding-block: Spacings.$spacing02; - padding-inline: Spacings.$spacing03; - overflow: hidden; - - .icon { - width: IconSizes.$normal; - } - - .label { - overflow: hidden; - background-color: var(--background-primary-1); - border-radius: Radius.$normal; - padding-inline: Spacings.$spacing05; - padding-block: Spacings.$spacing01; - white-space: nowrap; - font-size: Typography.$small; - height: 24px; - display: flex; - align-items: center; - - .label_text { - @include Typography.EllipsisOverflow; - } - - &.not_set { - color: var(--text-1); - background-color: transparent; - padding-inline: 0; - } - - &.unfolded_not_set { - width: 0; - } - } - } - - .right { - flex: 1; - font-size: Typography.$small; - padding-block: Spacings.$spacing02; - min-width: 50%; - - &.folded { - display: none; - } - } - } - - .options { - position: absolute; - background-color: var(--background-0); - width: 100%; - top: 100%; - border: 1px solid var(--border-2); - border-top: none; - border-radius: 0 0 Radius.$normal Radius.$normal; - overflow: hidden; - max-height: 180px; - overflow: auto; - z-index: ZIndexes.$overlay; - padding-block: Spacings.$spacing02; - - .option { - padding-inline: Spacings.$spacing03; - padding-block: Spacings.$spacing02; - cursor: pointer; - display: flex; - gap: Spacings.$spacing03; - align-items: center; - overflow: hidden; - - &:hover { - .option_name { - background-color: var(--background-primary-1); - } - } - - .icon { - width: IconSizes.$normal; - } - - .option_name { - @include Typography.EllipsisOverflow; - border: 1px solid var(--border-1); - border-radius: Radius.$normal; - padding-inline: Spacings.$spacing05; - padding-block: Spacings.$spacing01; - white-space: nowrap; - } - } - } -} diff --git a/frontend/lib/components/ui/SingleSelector/SingleSelector.tsx b/frontend/lib/components/ui/SingleSelector/SingleSelector.tsx deleted file mode 100644 index 788e90162..000000000 --- a/frontend/lib/components/ui/SingleSelector/SingleSelector.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { UUID } from "crypto"; -import { useState } from "react"; - -import { iconList } from "@/lib/helpers/iconList"; - -import styles from "./SingleSelector.module.scss"; - -import { Icon } from "../Icon/Icon"; -import { TextInput } from "../TextInput/TextInput"; - -export type SelectOptionProps = { - label: string; - value: T; -}; - -type SelectProps = { - options: SelectOptionProps[]; - onChange: (option: T) => void | Promise; - selectedOption: SelectOptionProps | undefined; - placeholder: string; - iconName: keyof typeof iconList; - onBackClick?: () => void; -}; - -export const SingleSelector = ({ - onChange, - options, - selectedOption, - placeholder, - iconName, - onBackClick, -}: SelectProps): JSX.Element => { - const [search, setSearch] = useState(""); - const [folded, setFolded] = useState(true); - const [updating, setUpdating] = useState(false); - - const filteredOptions = options.filter((option) => - option.label.toLowerCase().includes(search.toLowerCase()) - ); - - const handleOptionClick = async (option: SelectOptionProps) => { - try { - if (option !== selectedOption && !updating) { - setUpdating(true); - await onChange(option.value); - setUpdating(false); - } - setFolded(true); - } catch (error) { - console.error(error); - } - }; - - return ( -
-
-
{ - setFolded(!folded); - if (!folded) { - onBackClick?.(); - } - }} - > -
- -
- {(folded || selectedOption) && ( -
- - {selectedOption?.label ?? placeholder} - -
- )} -
- {!folded && ( -
- -
- )} -
- {!folded && ( -
- {filteredOptions.map((option) => ( -
{ - handleOptionClick(option).catch(console.error); - }} - > -
- -
- {option.label} -
- ))} -
- )} -
- ); -}; diff --git a/frontend/lib/components/ui/SmallTabs/SmallTabs.module.scss b/frontend/lib/components/ui/SmallTabs/SmallTabs.module.scss deleted file mode 100644 index 82e8cc837..000000000 --- a/frontend/lib/components/ui/SmallTabs/SmallTabs.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.tabs_container { - display: flex; - border-bottom: 1px solid var(--border-2); - position: relative; -} - -.tab_wrapper { - display: flex; - align-items: center; - justify-content: center; - padding-block: Spacings.$spacing02; - border-bottom: 1px solid transparent; - padding-inline: Spacings.$spacing05; - cursor: pointer; - box-sizing: border-box; - gap: Spacings.$spacing03; - margin-bottom: -1px; - z-index: 1; - - &.selected { - border-bottom-color: var(--primary-0); - color: var(--primary-0); - background-color: var(--background-2); - } - - &:hover { - color: var(--primary-0); - } - - &.disabled { - pointer-events: none; - color: var(--text-1); - } - - .label_wrapper { - display: flex; - position: relative; - gap: Spacings.$spacing02; - - .label { - font-size: Typography.$tiny; - font-weight: 500; - } - - .label_badge { - border-radius: Radius.$circle; - width: 16px; - height: 16px; - color: var(--white-0); - display: flex; - justify-content: center; - align-items: center; - background-color: var(--primary-0); - font-size: Typography.$very-tiny; - } - } -} diff --git a/frontend/lib/components/ui/SmallTabs/SmallTabs.tsx b/frontend/lib/components/ui/SmallTabs/SmallTabs.tsx deleted file mode 100644 index 71bd28e42..000000000 --- a/frontend/lib/components/ui/SmallTabs/SmallTabs.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Tab } from "@/lib/types/Tab"; - -import styles from "./SmallTabs.module.scss"; - -type TabsProps = { - tabList: Tab[]; -}; - -export const SmallTabs = ({ tabList }: TabsProps): JSX.Element => { - return ( -
- {tabList.map((tab, index) => ( -
-
- {tab.label} - {!!tab.badge && tab.badge > 0 && ( -
{tab.badge}
- )} -
-
- ))} -
- ); -}; diff --git a/frontend/lib/components/ui/Spinner.tsx b/frontend/lib/components/ui/Spinner.tsx deleted file mode 100644 index 08e780950..000000000 --- a/frontend/lib/components/ui/Spinner.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FaSpinner } from "react-icons/fa"; - -import { cn } from "@/lib/utils"; - -type SpinnerProps = { - className?: string; -}; -const Spinner = ({ className }: SpinnerProps): JSX.Element => { - return ; -}; - -export default Spinner; diff --git a/frontend/lib/components/ui/SwitchButton/SwitchButton.module.scss b/frontend/lib/components/ui/SwitchButton/SwitchButton.module.scss deleted file mode 100644 index 33a77e974..000000000 --- a/frontend/lib/components/ui/SwitchButton/SwitchButton.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.switch_wrapper { - display: flex; - gap: Spacings.$spacing03; - align-items: center; - font-size: Typography.$small; - font-weight: 500; - - .slider { - border-radius: Radius.$big; - height: 18px; - width: 42px; - background-color: var(--primary-2); - cursor: pointer; - - &.checked { - background-color: var(--primary-1); - - .slider_bubble { - margin-left: 26px; - background-color: var(--primary-0); - } - } - - .slider_bubble { - margin-left: Spacings.$spacing01; - margin-top: Spacings.$spacing01; - height: 14px; - width: 14px; - border-radius: Radius.$circle; - background-color: var(--primary-1); - transition: margin-left 0.2s ease-in-out; - } - } -} diff --git a/frontend/lib/components/ui/SwitchButton/SwitchButton.tsx b/frontend/lib/components/ui/SwitchButton/SwitchButton.tsx deleted file mode 100644 index 6d799482c..000000000 --- a/frontend/lib/components/ui/SwitchButton/SwitchButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import styles from "./SwitchButton.module.scss"; - -interface SwitchButtonProps { - label: string; - checked: boolean; - setChecked: (checked: boolean) => void; -} - -export const SwitchButton = ({ - label, - checked, - setChecked, -}: SwitchButtonProps): JSX.Element => { - const handleToggle = () => { - setChecked(!checked); - }; - - return ( -
- {label} -
-
-
-
- ); -}; - -export default SwitchButton; diff --git a/frontend/lib/components/ui/Tabs/Tabs.module.scss b/frontend/lib/components/ui/Tabs/Tabs.module.scss deleted file mode 100644 index 15f43558d..000000000 --- a/frontend/lib/components/ui/Tabs/Tabs.module.scss +++ /dev/null @@ -1,67 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/ScreenSizes.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.tabs_container { - display: flex; - justify-content: space-between; - - .tab_wrapper { - display: flex; - align-items: center; - justify-content: center; - flex: 1; - border-bottom: 1px solid var(--border-2); - padding-block: Spacings.$spacing03; - cursor: pointer; - box-sizing: border-box; - gap: Spacings.$spacing03; - - &.selected { - border-bottom-color: var(--primary-0); - color: var(--primary-0); - background-color: var(--background-2); - } - - &:hover { - color: var(--primary-0); - } - - &.disabled { - pointer-events: none; - color: var(--text-1); - } - - @media (max-width: ScreenSizes.$small) { - .icon { - display: none; - } - - .label_wrapper { - .label { - font-size: Typography.$very-tiny; - } - } - } - - .label_wrapper { - display: flex; - position: relative; - gap: Spacings.$spacing02; - font-size: Typography.$small; - - .label_badge { - border-radius: Radius.$circle; - width: 16px; - height: 16px; - color: var(--white-0); - display: flex; - justify-content: center; - align-items: center; - background-color: var(--primary-0); - font-size: Typography.$very-tiny; - } - } - } -} diff --git a/frontend/lib/components/ui/Tabs/Tabs.tsx b/frontend/lib/components/ui/Tabs/Tabs.tsx deleted file mode 100644 index e785898f7..000000000 --- a/frontend/lib/components/ui/Tabs/Tabs.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useState } from "react"; - -import { Tab } from "@/lib/types/Tab"; - -import styles from "./Tabs.module.scss"; - -import { Icon } from "../Icon/Icon"; - -type TabsProps = { - tabList: Tab[]; -}; - -export const Tabs = ({ tabList }: TabsProps): JSX.Element => { - const [tabHoveredIndex, setTabHoveredIndex] = useState(null); - - return ( -
- {tabList.map((tab, index) => ( -
setTabHoveredIndex(index)} - onMouseLeave={() => setTabHoveredIndex(null)} - > - -
- {tab.label} - {!!tab.badge && tab.badge > 0 && ( -
{tab.badge}
- )} -
-
- ))} -
- ); -}; diff --git a/frontend/lib/components/ui/Tag/Tag.module.scss b/frontend/lib/components/ui/Tag/Tag.module.scss deleted file mode 100644 index ca4c6ed34..000000000 --- a/frontend/lib/components/ui/Tag/Tag.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; - -.tag_wrapper { - padding: Spacings.$spacing01; - padding-inline: Spacings.$spacing03; - border-radius: Radius.$big; - width: fit-content; - font-size: Typography.$tiny; - font-weight: 600; - - &.primary { - color: var(--primary-0); - background-color: var(--background-primary-0); - } - - &.gold { - background-color: var(--gold); - } - - &.dangerous { - color: var(--dangerous-dark); - background-color: var(--background-error); - } - - &.success { - color: var(--success); - background-color: var(--background-success); - } - - &.grey { - color: var(--text-4); - background-color: var(--background-pending); - } -} diff --git a/frontend/lib/components/ui/Tag/Tag.tsx b/frontend/lib/components/ui/Tag/Tag.tsx deleted file mode 100644 index 4d465042c..000000000 --- a/frontend/lib/components/ui/Tag/Tag.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from "@/lib/types/Colors"; - -import styles from "./Tag.module.scss"; - -interface TagProps { - name: string; - color: Color; -} - -export const Tag = (props: TagProps): JSX.Element => { - return ( -
- {props.name} -
- ); -}; diff --git a/frontend/lib/components/ui/TextArea.tsx b/frontend/lib/components/ui/TextArea.tsx deleted file mode 100644 index 7179a927b..000000000 --- a/frontend/lib/components/ui/TextArea.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { - DetailedHTMLProps, - forwardRef, - InputHTMLAttributes, - RefObject, -} from "react"; - -import { cn } from "@/lib/utils"; - -interface FieldProps - extends DetailedHTMLProps< - InputHTMLAttributes, - HTMLTextAreaElement - > { - label?: string; - name: string; - inputClassName?: string; -} - -export const TextArea = forwardRef( - ( - { - label, - className, - inputClassName, - name, - required = false, - ...props - }: FieldProps, - forwardedRef - ) => { - return ( -
- {label !== undefined && ( - - )} -