diff --git a/.dockerignore b/.dockerignore index b501ad1a959..cd4afbe92f2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,4 @@ node_modules/ .svelte-kit/ .env* !.env -!.env.local \ No newline at end of file +.env.local \ No newline at end of file diff --git a/.env b/.env index cd4b15fd795..67993894d94 100644 --- a/.env +++ b/.env @@ -153,3 +153,6 @@ WEBHOOK_URL_REPORT_ASSISTANT=#provide webhook url to get notified when an assist ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to use the app USAGE_LIMITS=`{}` +ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls +METRICS_PORT= +LOG_LEVEL=info \ No newline at end of file diff --git a/.env.template b/.env.template index 6d42f780907..bcb0cb20ca6 100644 --- a/.env.template +++ b/.env.template @@ -10,8 +10,8 @@ MODELS=`[ "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/cohere-logo.png", "parameters": { "stop": ["<|END_OF_TURN_TOKEN|>"], - "truncate" : 24576, - "max_new_tokens" : 8192, + "truncate" : 28672, + "max_new_tokens" : 4096, "temperature" : 0.3 }, "promptExamples" : [ @@ -206,8 +206,9 @@ MODELS=`[ "modelUrl": "https://huggingface.co/microsoft/Phi-3-mini-4k-instruct", "websiteUrl": "https://azure.microsoft.com/en-us/blog/introducing-phi-3-redefining-whats-possible-with-slms/", "preprompt": "", + "chatPromptTemplate": "{{preprompt}}{{#each messages}}{{#ifUser}}<|user|>\n{{content}}<|end|>\n<|assistant|>\n{{/ifUser}}{{#ifAssistant}}{{content}}<|end|>\n{{/ifAssistant}}{{/each}}", "parameters": { - "stop": ["", "<|end|>", "<|endoftext|>", "<|assistant|>"], + "stop": ["<|end|>", "<|endoftext|>", "<|assistant|>"], "max_new_tokens": 1024, "truncate": 3071 }, @@ -230,6 +231,7 @@ MODELS=`[ "parameters": { "temperature": 0.1, "stop": ["<|eot_id|>"], + "truncate": 1024, }, "unlisted": true } diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000000..3a183679f1a --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,16 @@ +changelog: + exclude: + labels: + - huggingchat + - CI/CD + - documentation + categories: + - title: Features + labels: + - enhancement + - title: Bugfixes + labels: + - bug + - title: Other changes + labels: + - "*" diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index b284c9f6ec3..0238051484f 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -8,7 +8,7 @@ on: branches: - "*" paths: - - "Dockerfile.local" + - "Dockerfile" - "entrypoint.sh" workflow_dispatch: release: @@ -62,7 +62,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: Dockerfile.local + file: Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -116,7 +116,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: Dockerfile.local + file: Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 00000000000..2ebe452fa56 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,82 @@ +name: Deploy to k8s +on: + # run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build-and-publish-huggingchat-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Tailscale + uses: huggingface/tailscale-action@main + with: + authkey: ${{ secrets.TAILSCALE_AUTHKEY }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + registry.internal.huggingface.tech/chat-ui/chat-ui + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha,enable={{is_default_branch}} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Registry + uses: docker/login-action@v2 + with: + registry: registry.internal.huggingface.tech + username: ${{ secrets.DOCKER_INTERNAL_USERNAME }} + password: ${{ secrets.DOCKER_INTERNAL_PASSWORD }} + + - name: Build and Publish HuggingChat image + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + cache-to: type=gha,mode=max,scope=amd64 + cache-from: type=gha,scope=amd64 + provenance: false + build-args: | + INCLUDE_DB=false + APP_BASE=/chat + PUBLIC_APP_COLOR=yellow + + deploy: + name: Deploy on prod + runs-on: ubuntu-latest + needs: ["build-and-publish-huggingchat-image"] + steps: + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v4.5.0 + + - name: Gen values + run: | + VALUES=$(cat <<-END + image: + tag: "sha-${{ env.GITHUB_SHA_SHORT }}" + END + ) + echo "VALUES=$(echo "$VALUES" | yq -o=json | jq tostring)" >> $GITHUB_ENV + + - name: Deploy on infra-deployments + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: Update application values + repo: huggingface/infra-deployments + wait-for-completion: true + wait-for-completion-interval: 10s + display-workflow-run-url-interval: 10s + ref: refs/heads/main + token: ${{ secrets.ARGO_CD_TOKEN }} + inputs: '{"path": "hub/chat-ui/chat-ui.yaml", "values": ${{ env.VALUES }}, "url": "${{ github.event.head_commit.url }}"}' diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index a4d0b33ca9c..53094e3c7c2 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -1,4 +1,4 @@ -name: Deploy to production +name: Deploy to production spaces on: # run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index 14da9e54aab..00000000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Deploy to staging environment -on: - push: - branches: [main] - - # to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - sync-to-hub: - runs-on: ubuntu-latest - steps: - - name: Check large files - uses: ActionsDesk/lfs-warning@v2.0 - with: - filesizelimit: 10485760 # this is 10MB so we can sync to HF Spaces - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - lfs: true - - name: Push to hub - env: - HF_DEPLOYMENT_TOKEN: ${{ secrets.HF_DEPLOYMENT_TOKEN }} - run: git push https://nsarrazin:$HF_DEPLOYMENT_TOKEN@huggingface.co/spaces/huggingchat/chat-ui-staging main diff --git a/.prettierignore b/.prettierignore index 38972655faf..177a4e072ad 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,6 +3,7 @@ node_modules /build /.svelte-kit /package +/chart .env .env.* !.env.example diff --git a/Dockerfile b/Dockerfile index f87b5a64935..ca205fbbfb4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # syntax=docker/dockerfile:1 # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker # you will also find guides on how best to write your Dockerfile +ARG INCLUDE_DB=false + +# stage that install the dependencies FROM node:20 as builder-production WORKDIR /app @@ -12,31 +15,74 @@ RUN --mount=type=cache,target=/app/.npm \ FROM builder-production as builder +ARG APP_BASE= +ARG PUBLIC_APP_COLOR=blue + RUN --mount=type=cache,target=/app/.npm \ npm set cache /app/.npm && \ npm ci COPY --link --chown=1000 . . -RUN --mount=type=secret,id=DOTENV_LOCAL,dst=.env.local \ - npm run build +RUN npm run build -FROM node:20-slim -RUN npm install -g pm2 +# mongo image +FROM mongo:latest as mongo -RUN userdel -r node +# image to be used if INCLUDE_DB is false +FROM node:20-slim as local_db_false + +# image to be used if INCLUDE_DB is true +FROM node:20-slim as local_db_true + +RUN apt-get update +RUN apt-get install gnupg curl -y +# copy mongo from the other stage +COPY --from=mongo /usr/bin/mongo* /usr/bin/ + +ENV MONGODB_URL=mongodb://localhost:27017 +RUN mkdir -p /data/db +RUN chown -R 1000:1000 /data/db + +# final image +FROM local_db_${INCLUDE_DB} as final + +# build arg to determine if the database should be included +ARG INCLUDE_DB=false +ENV INCLUDE_DB=${INCLUDE_DB} + +# svelte requires APP_BASE at build time so it must be passed as a build arg +ARG APP_BASE= +# tailwind requires the primary theme to be known at build time so it must be passed as a build arg +ARG PUBLIC_APP_COLOR=blue -RUN useradd -m -u 1000 user +# install dotenv-cli +RUN npm install -g dotenv-cli + +# switch to a user that works for spaces +RUN userdel -r node +RUN useradd -m -u 1000 user USER user ENV HOME=/home/user \ PATH=/home/user/.local/bin:$PATH +WORKDIR /app -COPY --from=builder-production --chown=1000 /app/node_modules /app/node_modules -COPY --link --chown=1000 package.json /app/package.json -COPY --from=builder --chown=1000 /app/build /app/build +# add a .env.local if the user doesn't bind a volume to it +RUN touch /app/.env.local + +# get the default config, the entrypoint script and the server script +COPY --chown=1000 package.json /app/package.json +COPY --chown=1000 .env /app/.env +COPY --chown=1000 entrypoint.sh /app/entrypoint.sh COPY --chown=1000 gcp-*.json /app/ -CMD pm2 start /app/build/index.js -i $CPU_CORES --no-daemon +#import the build & dependencies +COPY --from=builder --chown=1000 /app/build /app/build +COPY --from=builder --chown=1000 /app/node_modules /app/node_modules + +RUN chmod +x /app/entrypoint.sh + +CMD ["/bin/bash", "-c", "/app/entrypoint.sh"] diff --git a/Dockerfile.local b/Dockerfile.local deleted file mode 100644 index c6046c3eb92..00000000000 --- a/Dockerfile.local +++ /dev/null @@ -1,28 +0,0 @@ -ARG INCLUDE_DB=false -FROM mongo:latest as mongo - -FROM node:20-slim as local_db_false - -FROM node:20-slim as local_db_true - -RUN apt-get update -RUN apt-get install gnupg curl -y - -COPY --from=mongo /usr/bin/mongo* /usr/bin/ - -FROM local_db_${INCLUDE_DB} as final -ARG INCLUDE_DB=false -ENV INCLUDE_DB=${INCLUDE_DB} - -WORKDIR /app - -COPY --link --chown=1000 package-lock.json package.json ./ -RUN --mount=type=cache,target=/app/.npm \ - npm set cache /app/.npm && \ - npm ci - -# copy the rest of the files, run regardless of -COPY --chown=1000 --link . . -RUN chmod +x /app/entrypoint.sh - -CMD ["/bin/bash", "-c", "/app/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md index c3f153b30b1..b485389fb18 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,9 @@ A chat interface using open source models, eg OpenAssistant or Llama. It is a Sv 3. [Web Search](#web-search) 4. [Text Embedding Models](#text-embedding-models) 5. [Extra parameters](#extra-parameters) -6. [Deploying to a HF Space](#deploying-to-a-hf-space) -7. [Building](#building) +6. [Common issues](#common-issues) +7. [Deploying to a HF Space](#deploying-to-a-hf-space) +8. [Building](#building) ## No Setup Deploy @@ -740,6 +741,14 @@ MODELS=`[ ]` ``` +## Common issues + +### 403:You don't have access to this conversation + +Most likely you are running chat-ui over HTTP. The recommended option is to setup something like NGINX to handle HTTPS and proxy the requests to chat-ui. If you really need to run over HTTP you can add `ALLOW_INSECURE_COOKIES=true` to your `.env.local`. + +Make sure to set your `PUBLIC_ORIGIN` in your `.env.local` to the correct URL as well. + ## Deploying to a HF Space Create a `DOTENV_LOCAL` secret to your HF space with the content of your .env.local, and they will be picked up automatically when you run. diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 00000000000..994382560cf --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: chat-ui +version: 0.0.0-latest +type: application +icon: https://huggingface.co/front/assets/huggingface_logo-noborder.svg diff --git a/chart/env/prod.yaml b/chart/env/prod.yaml new file mode 100644 index 00000000000..35665edcb95 --- /dev/null +++ b/chart/env/prod.yaml @@ -0,0 +1,335 @@ +image: + repository: registry.internal.huggingface.tech/chat-ui + name: chat-ui + +nodeSelector: + role-hub-utils: "true" + +tolerations: + - key: CriticalAddonsOnly + operator: Equal + +ingress: + path: "/chat" + annotations: + external-dns.alpha.kubernetes.io/hostname: "chat-ui.hub-alb.huggingface.tech" + alb.ingress.kubernetes.io/healthcheck-path: "/healthcheck" + alb.ingress.kubernetes.io/listen-ports: "[{\"HTTP\": 80}, {\"HTTPS\": 443}]" + alb.ingress.kubernetes.io/group.name: "hub-prod" + alb.ingress.kubernetes.io/scheme: "internet-facing" + alb.ingress.kubernetes.io/ssl-redirect: "443" + alb.ingress.kubernetes.io/tags: "Env=prod,Project=hub,Terraform=true" + alb.ingress.kubernetes.io/target-node-labels: "role-hub-utils=true" + kubernetes.io/ingress.class: "alb" + +envVars: + ADDRESS_HEADER: 'X-Forwarded-For' + ALTERNATIVE_REDIRECT_URLS: '["huggingchat://login/callback"]' + APP_BASE: "/chat" + ENABLE_ASSISTANTS: "true" + ENABLE_ASSISTANTS_RAG: "true" + EXPOSE_API: "true" + MESSAGES_BEFORE_LOGIN: 0 + METRICS_PORT: 5565 + LOG_LEVEL: "debug" + MODELS: > + [ + { + "name" : "CohereForAI/c4ai-command-r-plus", + "tokenizer": "Xenova/c4ai-command-r-v01-tokenizer", + "description": "Command R+ is Cohere's latest LLM and is the first open weight model to beat GPT4 in the Chatbot Arena!", + "modelUrl": "https://huggingface.co/CohereForAI/c4ai-command-r-plus", + "websiteUrl": "https://docs.cohere.com/docs/command-r-plus", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/cohere-logo.png", + "parameters": { + "stop": ["<|END_OF_TURN_TOKEN|>"], + "truncate" : 28672, + "max_new_tokens" : 4096, + "temperature" : 0.3 + }, + "promptExamples" : [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ] + }, + { + "name" : "meta-llama/Meta-Llama-3-70B-Instruct", + "description": "Generation over generation, Meta Llama 3 demonstrates state-of-the-art performance on a wide range of industry benchmarks and offers new capabilities, including improved reasoning.", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/meta-logo.png", + "modelUrl": "https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct", + "websiteUrl": "https://llama.meta.com/llama3/", + "tokenizer" : "philschmid/meta-llama-3-tokenizer", + "promptExamples" : [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ], + "parameters": { + "stop": ["<|eot_id|>"], + "truncate": 6144, + "max_new_tokens": 2047 + } + }, + { + "name" : "HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1", + "tokenizer": "HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1", + "description": "Zephyr 141B-A35B is a fine-tuned version of Mistral 8x22B, trained using ORPO, a novel alignment algorithm.", + "modelUrl": "https://huggingface.co/HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1", + "websiteUrl": "https://huggingface.co/HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/zephyr-logo.png", + "parameters": { + "truncate" : 24576, + "max_new_tokens" : 8192, + }, + "preprompt" : "You are Zephyr, an assistant developed by KAIST AI, Argilla, and Hugging Face. You should give concise responses to very simple questions, but provide thorough responses to more complex and open-ended questions. You are happy to help with writing, analysis, question answering, math, coding, and all sorts of other tasks.", + "promptExamples" : [ + { + "title": "Write a poem", + "prompt": "Write a poem to help me remember the first 10 elements on the periodic table, giving each element its own line." + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ] + }, + { + "name" : "mistralai/Mixtral-8x7B-Instruct-v0.1", + "description" : "The latest MoE model from Mistral AI! 8x7B and outperforms Llama 2 70B in most benchmarks.", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/mistral-logo.png", + "websiteUrl" : "https://mistral.ai/news/mixtral-of-experts/", + "modelUrl": "https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1", + "tokenizer": "mistralai/Mixtral-8x7B-Instruct-v0.1", + "preprompt" : "", + "chatPromptTemplate": " {{#each messages}}{{#ifUser}}[INST]{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}} {{content}} {{/ifAssistant}}{{/each}}", + "parameters" : { + "temperature" : 0.6, + "top_p" : 0.95, + "repetition_penalty" : 1.2, + "top_k" : 50, + "truncate" : 24576, + "max_new_tokens" : 8192, + "stop" : [""] + }, + "promptExamples" : [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ] + }, + { + "name" : "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO", + "description" : "Nous Hermes 2 Mixtral 8x7B DPO is the new flagship Nous Research model trained over the Mixtral 8x7B MoE LLM.", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/nous-logo.png", + "websiteUrl" : "https://nousresearch.com/", + "modelUrl": "https://huggingface.co/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO", + "tokenizer": "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO", + "chatPromptTemplate" : "{{#if @root.preprompt}}<|im_start|>system\n{{@root.preprompt}}<|im_end|>\n{{/if}}{{#each messages}}{{#ifUser}}<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n{{/ifUser}}{{#ifAssistant}}{{content}}<|im_end|>\n{{/ifAssistant}}{{/each}}", + "promptExamples": [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ], + "parameters": { + "temperature": 0.7, + "top_p": 0.95, + "repetition_penalty": 1, + "top_k": 50, + "truncate": 24576, + "max_new_tokens": 2048, + "stop": ["<|im_end|>"] + } + }, + { + "name" : "google/gemma-1.1-7b-it", + "description": "Gemma 7B 1.1 is the latest release in the Gemma family of lightweight models built by Google, trained using a novel RLHF method.", + "websiteUrl" : "https://blog.google/technology/developers/gemma-open-models/", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/google-logo.png", + "modelUrl": "https://huggingface.co/google/gemma-1.1-7b-it", + "preprompt": "", + "chatPromptTemplate" : "{{#each messages}}{{#ifUser}}user\n{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}\nmodel\n{{/ifUser}}{{#ifAssistant}}{{content}}\n{{/ifAssistant}}{{/each}}", + "promptExamples": [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ], + "parameters": { + "do_sample": true, + "truncate": 7168, + "max_new_tokens": 1024, + "stop" : [""] + } + }, + + { + "name": "mistralai/Mistral-7B-Instruct-v0.2", + "displayName": "mistralai/Mistral-7B-Instruct-v0.2", + "description": "Mistral 7B is a new Apache 2.0 model, released by Mistral AI that outperforms Llama2 13B in benchmarks.", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/mistral-logo.png", + "websiteUrl": "https://mistral.ai/news/announcing-mistral-7b/", + "modelUrl": "https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2", + "tokenizer": "mistralai/Mistral-7B-Instruct-v0.2", + "preprompt": "", + "chatPromptTemplate" : "{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}{{/ifAssistant}}{{/each}}", + "parameters": { + "temperature": 0.3, + "top_p": 0.95, + "repetition_penalty": 1.2, + "top_k": 50, + "truncate": 3072, + "max_new_tokens": 1024, + "stop": [""] + }, + "promptExamples": [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ] + }, + { + "name": "microsoft/Phi-3-mini-4k-instruct", + "tokenizer": "microsoft/Phi-3-mini-4k-instruct", + "description" : "Phi-3 Mini-4K-Instruct is a 3.8B parameters, lightweight, state-of-the-art open model built upon datasets used for Phi-2.", + "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/microsoft-logo.png", + "modelUrl": "https://huggingface.co/microsoft/Phi-3-mini-4k-instruct", + "websiteUrl": "https://azure.microsoft.com/en-us/blog/introducing-phi-3-redefining-whats-possible-with-slms/", + "preprompt": "", + "chatPromptTemplate": "{{preprompt}}{{#each messages}}{{#ifUser}}<|user|>\n{{content}}<|end|>\n<|assistant|>\n{{/ifUser}}{{#ifAssistant}}{{content}}<|end|>\n{{/ifAssistant}}{{/each}}", + "parameters": { + "stop": ["<|end|>", "<|endoftext|>", "<|assistant|>"], + "max_new_tokens": 1024, + "truncate": 3071 + }, + "promptExamples": [ + { + "title": "Write an email from bullet list", + "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)" + }, { + "title": "Code a snake game", + "prompt": "Code a basic snake game in python, give explanations for each step." + }, { + "title": "Assist in a task", + "prompt": "How do I make a delicious lemon cheesecake?" + } + ] + }, + { + "name": "meta-llama/Meta-Llama-3-8B-Instruct", + "tokenizer" : "philschmid/meta-llama-3-tokenizer", + "parameters": { + "temperature": 0.1, + "stop": ["<|eot_id|>"], + }, + "unlisted": true + } + ] + NODE_ENV: "prod" + NODE_LOG_STRUCTURED_DATA: true + OLD_MODELS: > + [ + { "name": "bigcode/starcoder" }, + { "name": "OpenAssistant/oasst-sft-6-llama-30b-xor" }, + { "name": "HuggingFaceH4/zephyr-7b-alpha" }, + { "name": "openchat/openchat_3.5" }, + { "name": "openchat/openchat-3.5-1210" }, + { "name": "tiiuae/falcon-180B-chat" }, + { "name": "codellama/CodeLlama-34b-Instruct-hf" }, + { "name": "google/gemma-7b-it" }, + { "name": "meta-llama/Llama-2-70b-chat-hf" }, + { "name": "codellama/CodeLlama-70b-Instruct-hf" }, + { "name": "openchat/openchat-3.5-0106" } + ] + PUBLIC_ORIGIN: "https://huggingface.co" + PUBLIC_SHARE_PREFIX: "https://hf.co/chat" + PUBLIC_ANNOUNCEMENT_BANNERS: "[]" + PUBLIC_APP_NAME: "HuggingChat" + PUBLIC_APP_ASSETS: "huggingchat" + PUBLIC_APP_COLOR: "yellow" + PUBLIC_APP_DESCRIPTION: "Making the community's best AI chat models available to everyone." + PUBLIC_APP_DISCLAIMER_MESSAGE: "Disclaimer: AI is an area of active research with known problems such as biased generation and misinformation. Do not use this application for high-stakes decisions or advice." + PUBLIC_APP_DATA_SHARING: 0 + PUBLIC_APP_DISCLAIMER: 1 + PUBLIC_PLAUSIBLE_SCRIPT_URL: "/js/script.js" + PUBLIC_APPLE_APP_ID: "6476778843" + REQUIRE_FEATURED_ASSISTANTS: "true" + TASK_MODEL: "meta-llama/Meta-Llama-3-8B-Instruct" + TEXT_EMBEDDING_MODELS: > + [{ + "name": "bge-base-en-v1-5-sxa", + "displayName": "bge-base-en-v1-5-sxa", + "chunkCharLength": 512, + "endpoints": [{ + "type": "tei", + "url": "https://huggingchat-tei.hf.space/" + }] + }] + WEBSEARCH_BLOCKLIST: '["youtube.com", "twitter.com"]' + XFF_DEPTH: '2' + +externalSecrets: + enabled: true + secretStoreName: "chat-ui-prod-secretstore" + secretName: "chat-ui-prod-secrets" + parameters: + MONGODB_URL: "hub-prod-chat-ui-mongodb-url" + OPENID_CONFIG: "hub-prod-chat-ui-openid-config" + SERPER_API_KEY: "hub-prod-chat-ui-serper-api-key" + HF_TOKEN: "hub-prod-chat-ui-hf-token" + WEBHOOK_URL_REPORT_ASSISTANT: "hub-prod-chat-ui-webhook-report-assistant" + ADMIN_API_SECRET: "hub-prod-chat-ui-admin-api-secret" + USAGE_LIMITS: "hub-prod-chat-ui-usage-limits" + +autoscaling: + enabled: true + minReplicas: 6 + maxReplicas: 30 + targetMemoryUtilizationPercentage: "70" + targetCPUUtilizationPercentage: "70" + +monitoring: + enabled: true diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 00000000000..eee5a181d22 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,22 @@ +{{- define "name" -}} +{{- default $.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "app.name" -}} +chat-ui +{{- end -}} + +{{- define "labels.standard" -}} +release: {{ $.Release.Name | quote }} +heritage: {{ $.Release.Service | quote }} +chart: "{{ include "name" . }}" +app: "{{ include "app.name" . }}" +{{- end -}} + +{{- define "labels.resolver" -}} +release: {{ $.Release.Name | quote }} +heritage: {{ $.Release.Service | quote }} +chart: "{{ include "name" . }}" +app: "{{ include "app.name" . }}-resolver" +{{- end -}} + diff --git a/chart/templates/config.yaml b/chart/templates/config.yaml new file mode 100644 index 00000000000..c4c803e9e5f --- /dev/null +++ b/chart/templates/config.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} +data: + {{- range $key, $value := $.Values.envVars }} + {{ $key }}: {{ $value | quote }} + {{- end }} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml new file mode 100644 index 00000000000..22f65956a2e --- /dev/null +++ b/chart/templates/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} +spec: + progressDeadlineSeconds: 600 + {{- if not $.Values.autoscaling.enabled }} + replicas: {{ .Values.replicas }} + {{- end }} + revisionHistoryLimit: 10 + selector: + matchLabels: {{ include "labels.standard" . | nindent 6 }} + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: {{ include "labels.standard" . | nindent 8 }} + {{- if $.Values.envVars.NODE_LOG_STRUCTURED_DATA }} + annotations: + co.elastic.logs/json.expand_keys: "true" + {{- end }} + spec: + containers: + - name: chat-ui + image: "{{ .Values.image.repository }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + failureThreshold: 30 + periodSeconds: 10 + httpGet: + path: {{ $.Values.envVars.APP_BASE | default "" }}/healthcheck + port: {{ $.Values.envVars.APP_PORT | default 3000 | int }} + livenessProbe: + failureThreshold: 30 + periodSeconds: 10 + httpGet: + path: {{ $.Values.envVars.APP_BASE | default "" }}/healthcheck + port: {{ $.Values.envVars.APP_PORT | default 3000 | int }} + ports: + - containerPort: {{ $.Values.envVars.APP_PORT | default 3000 | int }} + name: http + protocol: TCP + {{- if $.Values.monitoring.enabled }} + - containerPort: {{ $.Values.envVars.METRICS_PORT | default 5565 | int }} + name: metrics + protocol: TCP + {{- end }} + resources: {{ toYaml .Values.resources | nindent 12 }} + envFrom: + - configMapRef: + name: {{ include "name" . }} + {{- if $.Values.externalSecrets.enabled }} + - secretRef: + name: {{ $.Values.externalSecrets.secretName }} + {{- end }} + nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }} + tolerations: {{ toYaml .Values.tolerations | nindent 8 }} + volumes: + - name: config + configMap: + name: {{ include "name" . }} diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml new file mode 100644 index 00000000000..bf7bd3b256b --- /dev/null +++ b/chart/templates/hpa.yaml @@ -0,0 +1,45 @@ +{{- if $.Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "name" . }} + minReplicas: {{ $.Values.autoscaling.minReplicas }} + maxReplicas: {{ $.Values.autoscaling.maxReplicas }} + metrics: + {{- if ne "" $.Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ $.Values.autoscaling.targetMemoryUtilizationPercentage | int }} + {{- end }} + {{- if ne "" $.Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ $.Values.autoscaling.targetCPUUtilizationPercentage | int }} + {{- end }} + behavior: + scaleDown: + stabilizationWindowSeconds: 600 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Pods + value: 1 + periodSeconds: 30 +{{- end }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 00000000000..d507d946f60 --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: {{ toYaml .Values.ingress.annotations | nindent 4 }} + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} +spec: + rules: + - host: {{ .Values.domain }} + http: + paths: + - backend: + service: + name: {{ include "name" . }} + port: + name: http + path: {{ $.Values.ingress.path | default "/" }} + pathType: Prefix diff --git a/chart/templates/secrets.yaml b/chart/templates/secrets.yaml new file mode 100644 index 00000000000..494d9019859 --- /dev/null +++ b/chart/templates/secrets.yaml @@ -0,0 +1,21 @@ +{{- if .Values.externalSecrets.enabled }} +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" $ }}-external-secret + namespace: {{ $.Release.Namespace }} +spec: + refreshInterval: 1h + secretStoreRef: + name: {{ .Values.externalSecrets.secretStoreName }} + kind: SecretStore + target: + name: {{ .Values.externalSecrets.secretName }} + data: + {{- range $key, $value := .Values.externalSecrets.parameters }} + - secretKey: {{ $key | quote }} + remoteRef: + key: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/chart/templates/service-monitor.yaml b/chart/templates/service-monitor.yaml new file mode 100644 index 00000000000..2d7fdae07d8 --- /dev/null +++ b/chart/templates/service-monitor.yaml @@ -0,0 +1,15 @@ +{{- if $.Values.monitoring.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: {{ include "labels.standard" . | nindent 4 }} + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: {{ include "labels.standard" . | nindent 6 }} + endpoints: + - port: metrics + path: /metrics + interval: 15s +{{- end }} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 00000000000..682f8571fa5 --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ include "name" . }}" + annotations: {{ toYaml .Values.service.annotations | nindent 4 }} + namespace: {{ .Release.Namespace }} + labels: {{ include "labels.standard" . | nindent 4 }} +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + {{- if $.Values.monitoring.enabled }} + - name: metrics + port: 5565 + protocol: TCP + targetPort: metrics + {{- end }} + selector: {{ include "labels.standard" . | nindent 4 }} + type: {{.Values.service.type}} diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 00000000000..b9802e1264c --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,44 @@ +image: + repository: ghcr.io/huggingface + name: chat-ui + tag: 0.0.0-latest + pullPolicy: IfNotPresent + +replicas: 3 + +domain: huggingface.co + +service: + type: NodePort + annotations: { } + +ingress: + path: "/" + annotations: { } + +resources: + requests: + cpu: 1 + memory: 8Gi + limits: + cpu: 1 + memory: 8Gi +nodeSelector: {} +tolerations: [] + +envVars: { } +externalSecrets: + enabled: false + secretStoreName: "" + secretName: "" + parameters: { } + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 2 + targetMemoryUtilizationPercentage: "" + targetCPUUtilizationPercentage: "" + +monitoring: + enabled: false diff --git a/entrypoint.sh b/entrypoint.sh index c9a9f41fe49..3fc46ee53aa 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,7 +2,7 @@ ENV_LOCAL_PATH=/app/.env.local if test -z "${DOTENV_LOCAL}" ; then if ! test -f "${ENV_LOCAL_PATH}" ; then - echo "DOTENV_LOCAL was not found in the ENV variables and .env.local is not set using a bind volume. We are using the default .env config." + echo "DOTENV_LOCAL was not found in the ENV variables and .env.local is not set using a bind volume. Make sure to set environment variables properly. " fi; else echo "DOTENV_LOCAL was found in the ENV variables. Creating .env.local file." @@ -10,20 +10,10 @@ else fi; if [ "$INCLUDE_DB" = "true" ] ; then - echo "INCLUDE_DB is set to true." - - MONGODB_CONFIG="MONGODB_URL=mongodb://localhost:27017" - if ! grep -q "^${MONGODB_CONFIG}$" ${ENV_LOCAL_PATH}; then - echo "Appending MONGODB_URL" - touch /app/.env.local - echo -e "\n${MONGODB_CONFIG}" >> ${ENV_LOCAL_PATH} - fi - - mkdir -p /data/db - mongod & echo "Starting local MongoDB instance" - + nohup mongod & fi; -npm run build -npm run preview -- --host 0.0.0.0 --port 3000 \ No newline at end of file +export PUBLIC_VERSION=$(node -p "require('./package.json').version") + +dotenv -e /app/.env -c -- node /app/build/index.js -- --host 0.0.0.0 --port 3000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3ce12c34cdc..8a683fc33b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,13 @@ { "name": "chat-ui", - "version": "0.8.2", + "version": "0.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chat-ui", - "version": "0.8.2", + "version": "0.8.3", "dependencies": { - "@google-cloud/vertexai": "^1.1.0", "@huggingface/hub": "^0.5.1", "@huggingface/inference": "^2.6.3", "@iconify-json/bi": "^1.1.21", @@ -18,6 +17,7 @@ "browser-image-resizer": "^2.4.1", "date-fns": "^2.29.3", "dotenv": "^16.0.3", + "express": "^4.19.2", "handlebars": "^4.7.8", "highlight.js": "^11.7.0", "image-size": "^1.0.2", @@ -30,6 +30,8 @@ "nanoid": "^4.0.2", "openid-client": "^5.4.2", "parquetjs": "^0.11.2", + "pino": "^9.0.0", + "pino-pretty": "^11.0.0", "postcss": "^8.4.31", "saslprep": "^1.0.3", "satori": "^0.10.11", @@ -48,6 +50,7 @@ "@sveltejs/adapter-node": "^1.3.1", "@sveltejs/kit": "^1.30.4", "@tailwindcss/typography": "^0.5.9", + "@types/express": "^4.17.21", "@types/jsdom": "^21.1.1", "@types/minimist": "^1.2.5", "@types/parquetjs": "^0.10.3", @@ -61,6 +64,7 @@ "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "prettier-plugin-tailwindcss": "^0.2.7", + "prom-client": "^15.1.2", "svelte": "^4.2.8", "svelte-check": "^3.6.2", "ts-node": "^10.9.1", @@ -1262,6 +1266,15 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", + "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -1986,6 +1999,16 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", @@ -2001,6 +2024,15 @@ "@types/chai": "*" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", @@ -2013,6 +2045,36 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/jsdom": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.1.tgz", @@ -2040,6 +2102,12 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -2085,6 +2153,18 @@ "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", "dev": true }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2097,6 +2177,27 @@ "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -2452,7 +2553,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -2460,6 +2560,18 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -2590,6 +2702,11 @@ "dequal": "^2.0.3" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2613,6 +2730,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -2718,6 +2843,12 @@ "integrity": "sha512-u4cBQNepWxYA55FunZSM7wMi55yQaN0otnhhilNoWHq0MfOfJeQx0v0mRRpolGOExPjZcl6FtB0BB8Xkb88F0g==", "optional": true }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "dev": true + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -2734,6 +2865,67 @@ "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", "dev": true }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2866,6 +3058,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -2879,7 +3079,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "optional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3104,6 +3303,11 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3153,6 +3357,25 @@ "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" } }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -3162,6 +3385,11 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3314,6 +3542,14 @@ "node": ">=6" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3388,7 +3624,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "optional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3409,6 +3644,14 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3418,6 +3661,15 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -3522,6 +3774,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/electron-to-chromium": { "version": "1.4.359", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.359.tgz", @@ -3532,6 +3789,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -3555,7 +3820,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "optional": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -3567,7 +3831,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "optional": true, "engines": { "node": ">= 0.4" } @@ -3857,15 +4120,30 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true, "engines": { "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3897,12 +4175,93 @@ "node": ">=6" } }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "optional": true }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3958,6 +4317,19 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -3994,6 +4366,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4066,6 +4468,14 @@ "node": ">= 12.20" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -4078,6 +4488,14 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -4175,7 +4593,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "optional": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -4306,7 +4723,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "optional": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -4383,7 +4799,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "optional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -4395,7 +4810,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "optional": true, "engines": { "node": ">= 0.4" }, @@ -4407,7 +4821,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "optional": true, "engines": { "node": ">= 0.4" }, @@ -4431,6 +4844,11 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "node_modules/hex-rgb": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", @@ -4461,6 +4879,21 @@ "node": ">=12" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -4618,6 +5051,14 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -4756,6 +5197,14 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, "node_modules/js-base64": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", @@ -5172,11 +5621,24 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5191,6 +5653,14 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5203,6 +5673,17 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -5409,6 +5890,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -5566,7 +6055,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5587,6 +6075,25 @@ "node": "^10.13.0 || >=12.0.0" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5813,6 +6320,14 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5844,6 +6359,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5921,6 +6441,141 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.0.0.tgz", + "integrity": "sha512-uI1ThkzTShNSwvsUM6b4ND8ANzWURk9zTELMztFkmnCQeR/4wkomJ+echHee5GMWGovoSfjwdeu80DsFIt7mbA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.6.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -6314,6 +6969,32 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "node_modules/prom-client": { + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.2.tgz", + "integrity": "sha512-on3h1iXb04QFLLThrmVYg1SChBQ9N1c+nKAjebBjokBqipddH3uxmOUcEkTnzmJ8Jh/5TSUnUqS40i2QB2dJHQ==", + "dev": true, + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/protobufjs": { "version": "6.11.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", @@ -6339,6 +7020,18 @@ "pbts": "bin/pbts" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -6422,6 +7115,44 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -6482,6 +7213,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -6610,6 +7349,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6689,6 +7436,11 @@ "node": ">=v12.22.7" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6703,6 +7455,47 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/serpapi": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/serpapi/-/serpapi-1.1.1.tgz", @@ -6711,6 +7504,20 @@ "undici": "^5.12.0" } }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -6721,7 +7528,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "optional": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -6734,6 +7540,11 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/sharp": { "version": "0.33.2", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", @@ -6798,7 +7609,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "optional": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -6925,6 +7735,14 @@ "npm": ">= 3.0.0" } }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/sorcery": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", @@ -6964,6 +7782,14 @@ "memory-pager": "^1.0.2" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -6975,6 +7801,14 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", @@ -7040,7 +7874,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -7447,6 +8280,15 @@ "streamx": "^2.15.0" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "dev": true, + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7472,6 +8314,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/thrift": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.11.0.tgz", @@ -7544,6 +8394,14 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz", @@ -7694,6 +8552,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -7758,6 +8628,14 @@ "node": ">= 4.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unplugin": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.3.1.tgz", @@ -7862,6 +8740,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -7885,6 +8771,14 @@ "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", diff --git a/package.json b/package.json index ec36e759e4f..ef1d419c0c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chat-ui", - "version": "0.8.2", + "version": "0.8.3", "private": true, "packageManager": "npm@9.5.0", "scripts": { @@ -23,6 +23,7 @@ "@sveltejs/adapter-node": "^1.3.1", "@sveltejs/kit": "^1.30.4", "@tailwindcss/typography": "^0.5.9", + "@types/express": "^4.17.21", "@types/jsdom": "^21.1.1", "@types/minimist": "^1.2.5", "@types/parquetjs": "^0.10.3", @@ -36,6 +37,7 @@ "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "prettier-plugin-tailwindcss": "^0.2.7", + "prom-client": "^15.1.2", "svelte": "^4.2.8", "svelte-check": "^3.6.2", "ts-node": "^10.9.1", @@ -57,6 +59,7 @@ "browser-image-resizer": "^2.4.1", "date-fns": "^2.29.3", "dotenv": "^16.0.3", + "express": "^4.19.2", "handlebars": "^4.7.8", "highlight.js": "^11.7.0", "image-size": "^1.0.2", @@ -69,6 +72,8 @@ "nanoid": "^4.0.2", "openid-client": "^5.4.2", "parquetjs": "^0.11.2", + "pino": "^9.0.0", + "pino-pretty": "^11.0.0", "postcss": "^8.4.31", "saslprep": "^1.0.3", "satori": "^0.10.11", diff --git a/scripts/populate.ts b/scripts/populate.ts index 2a3e12a541e..8e06879a634 100644 --- a/scripts/populate.ts +++ b/scripts/populate.ts @@ -2,12 +2,13 @@ import readline from "readline"; import minimist from "minimist"; // @ts-expect-error: vite-node makes the var available but the typescript compiler doesn't see them -import { MONGODB_URL } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { faker } from "@faker-js/faker"; import { ObjectId } from "mongodb"; -import { collections } from "../src/lib/server/database.ts"; +// @ts-expect-error: vite-node makes the var available but the typescript compiler doesn't see them +import { collections } from "$lib/server/database"; import { models } from "../src/lib/server/models.ts"; import type { User } from "../src/lib/types/User"; import type { Assistant } from "../src/lib/types/Assistant"; @@ -17,6 +18,7 @@ import { defaultEmbeddingModel } from "../src/lib/server/embeddingModels.ts"; import { Message } from "../src/lib/types/Message.ts"; import { addChildren } from "../src/lib/utils/tree/addChildren.ts"; +import { generateSearchTokens } from "../src/lib/utils/searchTokens.ts"; const rl = readline.createInterface({ input: process.stdin, @@ -158,10 +160,11 @@ async function seed() { console.log("Creating assistants for all users"); await Promise.all( users.map(async (user) => { + const name = faker.animal.insect(); const assistants = faker.helpers.multiple( () => ({ _id: new ObjectId(), - name: faker.animal.insect(), + name, createdById: user._id, createdByName: user.username, createdAt: faker.date.recent({ days: 30 }), @@ -174,6 +177,8 @@ async function seed() { exampleInputs: faker.helpers.multiple(() => faker.lorem.sentence(), { count: faker.number.int({ min: 0, max: 4 }), }), + searchTokens: generateSearchTokens(name), + last24HoursCount: faker.number.int({ min: 0, max: 1000 }), }), { count: faker.number.int({ min: 3, max: 10 }) } ); @@ -241,7 +246,7 @@ async function seed() { try { rl.question( "You're about to run a seeding script on the following MONGODB_URL: \x1b[31m" + - MONGODB_URL + + env.MONGODB_URL + "\x1b[0m\n\n With the following flags: \x1b[31m" + flags.join("\x1b[0m , \x1b[31m") + "\x1b[0m\n \n\n Are you sure you want to continue? (yes/no): ", diff --git a/src/app.d.ts b/src/app.d.ts index dfd942be68d..681fd365433 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -12,6 +12,11 @@ declare global { sessionId: string; user?: User; } + + interface Error { + message: string; + errorId?: ReturnType; + } // interface PageData {} // interface Platform {} } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index d749c38e531..ff4f044eca4 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,17 +1,6 @@ -import { - ADMIN_API_SECRET, - COOKIE_NAME, - ENABLE_ASSISTANTS, - EXPOSE_API, - MESSAGES_BEFORE_LOGIN, - PARQUET_EXPORT_SECRET, -} from "$env/static/private"; -import type { Handle } from "@sveltejs/kit"; -import { - PUBLIC_GOOGLE_ANALYTICS_ID, - PUBLIC_ORIGIN, - PUBLIC_APP_DISCLAIMER, -} from "$env/static/public"; +import { env } from "$env/dynamic/private"; +import { env as envPublic } from "$env/dynamic/public"; +import type { Handle, HandleServerError } from "@sveltejs/kit"; import { collections } from "$lib/server/database"; import { base } from "$app/paths"; import { findUser, refreshSessionCookie, requiresUser } from "$lib/server/auth"; @@ -21,16 +10,63 @@ import { addWeeks } from "date-fns"; import { checkAndRunMigrations } from "$lib/migrations/migrations"; import { building } from "$app/environment"; import { refreshAssistantsCounts } from "$lib/assistantStats/refresh-assistants-counts"; +import { logger } from "$lib/server/logger"; +import { AbortedGenerations } from "$lib/server/abortedGenerations"; +import { MetricsServer } from "$lib/server/metrics"; +// TODO: move this code on a started server hook, instead of using a "building" flag if (!building) { await checkAndRunMigrations(); - if (ENABLE_ASSISTANTS) { + if (env.ENABLE_ASSISTANTS) { refreshAssistantsCounts(); } + + // Init metrics server + MetricsServer.getInstance(); + + // Init AbortedGenerations refresh process + AbortedGenerations.getInstance(); } +export const handleError: HandleServerError = async ({ error, event }) => { + // handle 404 + + if (building) { + throw error; + } + + if (event.route.id === null) { + return { + message: `Page ${event.url.pathname} not found`, + }; + } + + const errorId = crypto.randomUUID(); + + logger.error({ + locals: event.locals, + url: event.request.url, + params: event.params, + request: event.request, + error, + errorId, + }); + + return { + message: "An error occurred", + errorId, + }; +}; + export const handle: Handle = async ({ event, resolve }) => { - if (event.url.pathname.startsWith(`${base}/api/`) && EXPOSE_API !== "true") { + logger.debug({ + locals: event.locals, + url: event.url.pathname, + params: event.params, + request: event.request, + }); + + if (event.url.pathname.startsWith(`${base}/api/`) && env.EXPOSE_API !== "true") { return new Response("API is disabled", { status: 403 }); } @@ -47,7 +83,7 @@ export const handle: Handle = async ({ event, resolve }) => { } if (event.url.pathname.startsWith(`${base}/admin/`) || event.url.pathname === `${base}/admin`) { - const ADMIN_SECRET = ADMIN_API_SECRET || PARQUET_EXPORT_SECRET; + const ADMIN_SECRET = env.ADMIN_API_SECRET || env.PARQUET_EXPORT_SECRET; if (!ADMIN_SECRET) { return errorResponse(500, "Admin API is not configured"); @@ -58,7 +94,7 @@ export const handle: Handle = async ({ event, resolve }) => { } } - const token = event.cookies.get(COOKIE_NAME); + const token = event.cookies.get(env.COOKIE_NAME); let secretSessionId: string; let sessionId: string; @@ -97,18 +133,18 @@ export const handle: Handle = async ({ event, resolve }) => { refreshSessionCookie(event.cookies, event.locals.sessionId); if (nativeFormContentTypes.includes(requestContentType)) { - const referer = event.request.headers.get("referer"); + const origin = event.request.headers.get("origin"); - if (!referer) { - return errorResponse(403, "Non-JSON form requests need to have a referer"); + if (!origin) { + return errorResponse(403, "Non-JSON form requests need to have an origin"); } const validOrigins = [ - new URL(event.request.url).origin, - ...(PUBLIC_ORIGIN ? [new URL(PUBLIC_ORIGIN).origin] : []), + new URL(event.request.url).host, + ...(envPublic.PUBLIC_ORIGIN ? [new URL(envPublic.PUBLIC_ORIGIN).host] : []), ]; - if (!validOrigins.includes(new URL(referer).origin)) { + if (!validOrigins.includes(new URL(origin).host)) { return errorResponse(403, "Invalid referer for POST request"); } } @@ -132,7 +168,7 @@ export const handle: Handle = async ({ event, resolve }) => { if ( !event.locals.user && requiresUser && - !((MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0) + !((env.MESSAGES_BEFORE_LOGIN ? parseInt(env.MESSAGES_BEFORE_LOGIN) : 0) > 0) ) { return errorResponse(401, ERROR_MESSAGES.authOnly); } @@ -143,7 +179,7 @@ export const handle: Handle = async ({ event, resolve }) => { if ( !requiresUser && !event.url.pathname.startsWith(`${base}/settings`) && - !!PUBLIC_APP_DISCLAIMER + !!envPublic.PUBLIC_APP_DISCLAIMER ) { const hasAcceptedEthicsModal = await collections.settings.countDocuments({ sessionId: event.locals.sessionId, @@ -166,7 +202,7 @@ export const handle: Handle = async ({ event, resolve }) => { } replaced = true; - return chunk.html.replace("%gaId%", PUBLIC_GOOGLE_ANALYTICS_ID); + return chunk.html.replace("%gaId%", envPublic.PUBLIC_GOOGLE_ANALYTICS_ID); }, }); diff --git a/src/lib/assistantStats/refresh-assistants-counts.ts b/src/lib/assistantStats/refresh-assistants-counts.ts index 4376202638c..a5bb5f52596 100644 --- a/src/lib/assistantStats/refresh-assistants-counts.ts +++ b/src/lib/assistantStats/refresh-assistants-counts.ts @@ -1,7 +1,8 @@ -import { client, collections } from "$lib/server/database"; +import { Database } from "$lib/server/database"; import { acquireLock, refreshLock } from "$lib/migrations/lock"; import type { ObjectId } from "mongodb"; import { subDays } from "date-fns"; +import { logger } from "$lib/server/logger"; const LOCK_KEY = "assistants.count"; @@ -14,47 +15,52 @@ async function refreshAssistantsCountsHelper() { } try { - await client.withSession((session) => - session.withTransaction(async () => { - await collections.assistants - .aggregate([ - { $project: { _id: 1 } }, - { $set: { last24HoursCount: 0 } }, - { - $unionWith: { - coll: "assistants.stats", - pipeline: [ - { $match: { "date.at": { $gte: subDays(new Date(), 1) }, "date.span": "hour" } }, - { - $group: { - _id: "$assistantId", - last24HoursCount: { $sum: "$count" }, + await Database.getInstance() + .getClient() + .withSession((session) => + session.withTransaction(async () => { + await Database.getInstance() + .getCollections() + .assistants.aggregate([ + { $project: { _id: 1 } }, + { $set: { last24HoursCount: 0 } }, + { + $unionWith: { + coll: "assistants.stats", + pipeline: [ + { + $match: { "date.at": { $gte: subDays(new Date(), 1) }, "date.span": "hour" }, }, - }, - ], + { + $group: { + _id: "$assistantId", + last24HoursCount: { $sum: "$count" }, + }, + }, + ], + }, }, - }, - { - $group: { - _id: "$_id", - last24HoursCount: { $sum: "$last24HoursCount" }, + { + $group: { + _id: "$_id", + last24HoursCount: { $sum: "$last24HoursCount" }, + }, }, - }, - { - $merge: { - into: "assistants", - on: "_id", - whenMatched: "merge", - whenNotMatched: "discard", + { + $merge: { + into: "assistants", + on: "_id", + whenMatched: "merge", + whenNotMatched: "discard", + }, }, - }, - ]) - .next(); - }) - ); + ]) + .next(); + }) + ); } catch (e) { - console.log("Refresh assistants counter failed!"); - console.error(e); + logger.error("Refresh assistants counter failed!"); + logger.error(e); } } diff --git a/src/lib/components/AnnouncementBanner.svelte b/src/lib/components/AnnouncementBanner.svelte index 7d6948a6b38..47ec7b02ef2 100644 --- a/src/lib/components/AnnouncementBanner.svelte +++ b/src/lib/components/AnnouncementBanner.svelte @@ -5,7 +5,7 @@
New {title} diff --git a/src/lib/components/CopyToClipBoardBtn.svelte b/src/lib/components/CopyToClipBoardBtn.svelte index 53f4c122c66..bafb71c9a58 100644 --- a/src/lib/components/CopyToClipBoardBtn.svelte +++ b/src/lib/components/CopyToClipBoardBtn.svelte @@ -43,7 +43,7 @@ >
- + diff --git a/src/lib/components/DisclaimerModal.svelte b/src/lib/components/DisclaimerModal.svelte index 590bb088b3c..f0a18abd1dd 100644 --- a/src/lib/components/DisclaimerModal.svelte +++ b/src/lib/components/DisclaimerModal.svelte @@ -1,11 +1,7 @@
- + - {PUBLIC_APP_NAME} + {envPublic.PUBLIC_APP_NAME} Settings - {#if PUBLIC_APP_NAME === "HuggingChat"} + {#if envPublic.PUBLIC_APP_NAME === "HuggingChat"} 0 || assistant?.dynamicPrompt; - const prefix = PUBLIC_SHARE_PREFIX || `${PUBLIC_ORIGIN || $page.url.origin}${base}`; + const prefix = + envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || $page.url.origin}${base}`; $: shareUrl = `${prefix}/assistant/${assistant?._id}`; diff --git a/src/lib/components/chat/ChatIntroduction.svelte b/src/lib/components/chat/ChatIntroduction.svelte index eb0a11b0537..81fb51b09fa 100644 --- a/src/lib/components/chat/ChatIntroduction.svelte +++ b/src/lib/components/chat/ChatIntroduction.svelte @@ -1,7 +1,5 @@ -{#if PUBLIC_APP_ASSETS === "chatui"} +{#if envPublic.PUBLIC_APP_ASSETS === "chatui"} {/if} diff --git a/src/lib/migrations/migrations.ts b/src/lib/migrations/migrations.ts index 2615f170b5f..2331644d8b8 100644 --- a/src/lib/migrations/migrations.ts +++ b/src/lib/migrations/migrations.ts @@ -1,7 +1,8 @@ -import { client, collections } from "$lib/server/database"; +import { Database } from "$lib/server/database"; import { migrations } from "./routines"; import { acquireLock, releaseLock, isDBLocked, refreshLock } from "./lock"; import { isHuggingChat } from "$lib/utils/isHuggingChat"; +import { logger } from "$lib/server/logger"; const LOCK_KEY = "migrations"; @@ -12,18 +13,21 @@ export async function checkAndRunMigrations() { } // check if all migrations have already been run - const migrationResults = await collections.migrationResults.find().toArray(); + const migrationResults = await Database.getInstance() + .getCollections() + .migrationResults.find() + .toArray(); - console.log("[MIGRATIONS] Begin check..."); + logger.info("[MIGRATIONS] Begin check..."); // connect to the database - const connectedClient = await client.connect(); + const connectedClient = await Database.getInstance().getClient().connect(); const lockId = await acquireLock(LOCK_KEY); if (!lockId) { // another instance already has the lock, so we exit early - console.log( + logger.info( "[MIGRATIONS] Another instance already has the lock. Waiting for DB to be unlocked." ); @@ -50,65 +54,69 @@ export async function checkAndRunMigrations() { // check if the migration has already been applied if (!shouldRun) { - console.log(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`); + logger.info(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`); } else { // check the modifiers to see if some cases match if ( (migration.runForHuggingChat === "only" && !isHuggingChat) || (migration.runForHuggingChat === "never" && isHuggingChat) ) { - console.log( + logger.info( `[MIGRATIONS] "${migration.name}" should not be applied for this run. Skipping...` ); continue; } // otherwise all is good and we can run the migration - console.log( + logger.info( `[MIGRATIONS] "${migration.name}" ${ migration.runEveryTime ? "should run every time" : "not applied yet" }. Applying...` ); - await collections.migrationResults.updateOne( - { _id: migration._id }, - { - $set: { - name: migration.name, - status: "ongoing", + await Database.getInstance() + .getCollections() + .migrationResults.updateOne( + { _id: migration._id }, + { + $set: { + name: migration.name, + status: "ongoing", + }, }, - }, - { upsert: true } - ); + { upsert: true } + ); const session = connectedClient.startSession(); let result = false; try { await session.withTransaction(async () => { - result = await migration.up(connectedClient); + result = await migration.up(Database.getInstance()); }); } catch (e) { - console.log(`[MIGRATION[] "${migration.name}" failed!`); - console.error(e); + logger.info(`[MIGRATIONS] "${migration.name}" failed!`); + logger.error(e); } finally { await session.endSession(); } - await collections.migrationResults.updateOne( - { _id: migration._id }, - { - $set: { - name: migration.name, - status: result ? "success" : "failure", + await Database.getInstance() + .getCollections() + .migrationResults.updateOne( + { _id: migration._id }, + { + $set: { + name: migration.name, + status: result ? "success" : "failure", + }, }, - }, - { upsert: true } - ); + { upsert: true } + ); } } - console.log("[MIGRATIONS] All migrations applied. Releasing lock"); + logger.info("[MIGRATIONS] All migrations applied. Releasing lock"); clearInterval(refreshInterval); await releaseLock(LOCK_KEY, lockId); diff --git a/src/lib/migrations/routines/01-update-search-assistants.ts b/src/lib/migrations/routines/01-update-search-assistants.ts index 9f12b27d3fb..52c8b2f6c99 100644 --- a/src/lib/migrations/routines/01-update-search-assistants.ts +++ b/src/lib/migrations/routines/01-update-search-assistants.ts @@ -1,5 +1,5 @@ import type { Migration } from "."; -import { getCollections } from "$lib/server/database"; +import { collections } from "$lib/server/database"; import { ObjectId, type AnyBulkWriteOperation } from "mongodb"; import type { Assistant } from "$lib/types/Assistant"; import { generateSearchTokens } from "$lib/utils/searchTokens"; @@ -7,8 +7,8 @@ import { generateSearchTokens } from "$lib/utils/searchTokens"; const migration: Migration = { _id: new ObjectId("5f9f3e3e3e3e3e3e3e3e3e3e"), name: "Update search assistants", - up: async (client) => { - const { assistants } = getCollections(client); + up: async () => { + const { assistants } = collections; let ops: AnyBulkWriteOperation[] = []; for await (const assistant of assistants @@ -40,8 +40,8 @@ const migration: Migration = { return true; }, - down: async (client) => { - const { assistants } = getCollections(client); + down: async () => { + const { assistants } = collections; await assistants.updateMany({}, { $unset: { searchTokens: "" } }); return true; }, diff --git a/src/lib/migrations/routines/02-update-assistants-models.ts b/src/lib/migrations/routines/02-update-assistants-models.ts index 73655a88f84..f7f0c9dd454 100644 --- a/src/lib/migrations/routines/02-update-assistants-models.ts +++ b/src/lib/migrations/routines/02-update-assistants-models.ts @@ -1,14 +1,14 @@ import type { Migration } from "."; -import { getCollections } from "$lib/server/database"; +import { collections } from "$lib/server/database"; import { ObjectId } from "mongodb"; const updateAssistantsModels: Migration = { _id: new ObjectId("5f9f3f3f3f3f3f3f3f3f3f3f"), name: "Update deprecated models in assistants with the default model", - up: async (client) => { + up: async () => { const models = (await import("$lib/server/models")).models; - const { assistants } = getCollections(client); + const { assistants } = collections; const modelIds = models.map((el) => el.id); // string[] const defaultModelId = models[0].id; diff --git a/src/lib/migrations/routines/index.ts b/src/lib/migrations/routines/index.ts index 96a6a07ab3c..0d6eafa8f04 100644 --- a/src/lib/migrations/routines/index.ts +++ b/src/lib/migrations/routines/index.ts @@ -1,13 +1,14 @@ -import type { MongoClient, ObjectId } from "mongodb"; +import type { ObjectId } from "mongodb"; import updateSearchAssistant from "./01-update-search-assistants"; import updateAssistantsModels from "./02-update-assistants-models"; +import type { Database } from "$lib/server/database"; export interface Migration { _id: ObjectId; name: string; - up: (client: MongoClient) => Promise; - down?: (client: MongoClient) => Promise; + up: (client: Database) => Promise; + down?: (client: Database) => Promise; runForFreshInstall?: "only" | "never"; // leave unspecified to run for both runForHuggingChat?: "only" | "never"; // leave unspecified to run for both runEveryTime?: boolean; diff --git a/src/lib/server/abortedGenerations.ts b/src/lib/server/abortedGenerations.ts index 575cf637bfe..548809d6b6c 100644 --- a/src/lib/server/abortedGenerations.ts +++ b/src/lib/server/abortedGenerations.ts @@ -1,29 +1,42 @@ // Shouldn't be needed if we dove into sveltekit internals, see https://github.com/huggingface/chat-ui/pull/88#issuecomment-1523173850 -import { setTimeout } from "node:timers/promises"; -import { collections } from "./database"; +import { logger } from "$lib/server/logger"; +import { collections } from "$lib/server/database"; -let closed = false; -process.on("SIGINT", () => { - closed = true; -}); +export class AbortedGenerations { + private static instance: AbortedGenerations; -export let abortedGenerations: Map = new Map(); + private abortedGenerations: Map = new Map(); -async function maintainAbortedGenerations() { - while (!closed) { - await setTimeout(1000); + private constructor() { + const interval = setInterval(this.updateList, 1000); + process.on("SIGINT", () => { + clearInterval(interval); + }); + } + + public static getInstance(): AbortedGenerations { + if (!AbortedGenerations.instance) { + AbortedGenerations.instance = new AbortedGenerations(); + } + + return AbortedGenerations.instance; + } + + public getList(): Map { + return this.abortedGenerations; + } + + private async updateList() { try { const aborts = await collections.abortedGenerations.find({}).sort({ createdAt: 1 }).toArray(); - abortedGenerations = new Map( + this.abortedGenerations = new Map( aborts.map(({ conversationId, createdAt }) => [conversationId.toString(), createdAt]) ); } catch (err) { - console.error(err); + logger.error(err); } } } - -maintainAbortedGenerations(); diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 96e6ec8f345..94eacdb476b 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,22 +1,13 @@ import { Issuer, BaseClient, type UserinfoResponse, TokenSet, custom } from "openid-client"; import { addHours, addWeeks } from "date-fns"; -import { - COOKIE_NAME, - OPENID_CLIENT_ID, - OPENID_CLIENT_SECRET, - OPENID_PROVIDER_URL, - OPENID_SCOPES, - OPENID_NAME_CLAIM, - OPENID_TOLERANCE, - OPENID_RESOURCE, - OPENID_CONFIG, -} from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { sha256 } from "$lib/utils/sha256"; import { z } from "zod"; import { dev } from "$app/environment"; import type { Cookies } from "@sveltejs/kit"; -import { collections } from "./database"; +import { collections } from "$lib/server/database"; import JSON5 from "json5"; +import { logger } from "$lib/server/logger"; export interface OIDCSettings { redirectURI: string; @@ -35,27 +26,27 @@ const stringWithDefault = (value: string) => export const OIDConfig = z .object({ - CLIENT_ID: stringWithDefault(OPENID_CLIENT_ID), - CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET), - PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL), - SCOPES: stringWithDefault(OPENID_SCOPES), - NAME_CLAIM: stringWithDefault(OPENID_NAME_CLAIM).refine( + CLIENT_ID: stringWithDefault(env.OPENID_CLIENT_ID), + CLIENT_SECRET: stringWithDefault(env.OPENID_CLIENT_SECRET), + PROVIDER_URL: stringWithDefault(env.OPENID_PROVIDER_URL), + SCOPES: stringWithDefault(env.OPENID_SCOPES), + NAME_CLAIM: stringWithDefault(env.OPENID_NAME_CLAIM).refine( (el) => !["preferred_username", "email", "picture", "sub"].includes(el), { message: "nameClaim cannot be one of the restricted keys." } ), - TOLERANCE: stringWithDefault(OPENID_TOLERANCE), - RESOURCE: stringWithDefault(OPENID_RESOURCE), + TOLERANCE: stringWithDefault(env.OPENID_TOLERANCE), + RESOURCE: stringWithDefault(env.OPENID_RESOURCE), }) - .parse(JSON5.parse(OPENID_CONFIG)); + .parse(JSON5.parse(env.OPENID_CONFIG)); export const requiresUser = !!OIDConfig.CLIENT_ID && !!OIDConfig.CLIENT_SECRET; export function refreshSessionCookie(cookies: Cookies, sessionId: string) { - cookies.set(COOKIE_NAME, sessionId, { + cookies.set(env.COOKIE_NAME, sessionId, { path: "/", // So that it works inside the space's iframe - sameSite: dev ? "lax" : "none", - secure: !dev, + sameSite: dev || env.ALLOW_INSECURE_COOKIES === "true" ? "lax" : "none", + secure: !dev && !(env.ALLOW_INSECURE_COOKIES === "true"), httpOnly: true, expires: addWeeks(new Date(), 2), }); @@ -150,7 +141,7 @@ export async function validateAndParseCsrfToken( return { redirectUrl: data.redirectUrl }; } } catch (e) { - console.error(e); + logger.error(e); } return null; } diff --git a/src/lib/server/database.ts b/src/lib/server/database.ts index fa6d2b8f0ff..4a8302ce9ca 100644 --- a/src/lib/server/database.ts +++ b/src/lib/server/database.ts @@ -1,4 +1,4 @@ -import { MONGODB_URL, MONGODB_DB_NAME, MONGODB_DIRECT_CONNECTION } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { GridFSBucket, MongoClient } from "mongodb"; import type { Conversation } from "$lib/types/Conversation"; import type { SharedConversation } from "$lib/types/SharedConversation"; @@ -13,149 +13,197 @@ import type { ConversationStats } from "$lib/types/ConversationStats"; import type { MigrationResult } from "$lib/types/MigrationResult"; import type { Semaphore } from "$lib/types/Semaphore"; import type { AssistantStats } from "$lib/types/AssistantStats"; +import { logger } from "$lib/server/logger"; +import { building } from "$app/environment"; -if (!MONGODB_URL) { - throw new Error( - "Please specify the MONGODB_URL environment variable inside .env.local. Set it to mongodb://localhost:27017 if you are running MongoDB locally, or to a MongoDB Atlas free instance for example." - ); -} export const CONVERSATION_STATS_COLLECTION = "conversations.stats"; -const client = new MongoClient(MONGODB_URL, { - directConnection: MONGODB_DIRECT_CONNECTION === "true", -}); - -export const connectPromise = client.connect().catch(console.error); - -export function getCollections(mongoClient: MongoClient) { - const db = mongoClient.db(MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : "")); - - const conversations = db.collection("conversations"); - const conversationStats = db.collection(CONVERSATION_STATS_COLLECTION); - const assistants = db.collection("assistants"); - const assistantStats = db.collection("assistants.stats"); - const reports = db.collection("reports"); - const sharedConversations = db.collection("sharedConversations"); - const abortedGenerations = db.collection("abortedGenerations"); - const settings = db.collection("settings"); - const users = db.collection("users"); - const sessions = db.collection("sessions"); - const messageEvents = db.collection("messageEvents"); - const bucket = new GridFSBucket(db, { bucketName: "files" }); - const migrationResults = db.collection("migrationResults"); - const semaphores = db.collection("semaphores"); - - return { - conversations, - conversationStats, - assistants, - assistantStats, - reports, - sharedConversations, - abortedGenerations, - settings, - users, - sessions, - messageEvents, - bucket, - migrationResults, - semaphores, - }; -} -const db = client.db(MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : "")); - -const collections = getCollections(client); - -const { - conversations, - conversationStats, - assistants, - assistantStats, - reports, - sharedConversations, - abortedGenerations, - settings, - users, - sessions, - messageEvents, - semaphores, -} = collections; - -export { client, db, collections }; - -client.on("open", () => { - conversations - .createIndex( - { sessionId: 1, updatedAt: -1 }, - { partialFilterExpression: { sessionId: { $exists: true } } } - ) - .catch(console.error); - conversations - .createIndex( - { userId: 1, updatedAt: -1 }, - { partialFilterExpression: { userId: { $exists: true } } } - ) - .catch(console.error); - conversations - .createIndex( - { "message.id": 1, "message.ancestors": 1 }, - { partialFilterExpression: { userId: { $exists: true } } } - ) - .catch(console.error); - // To do stats on conversations - conversations.createIndex({ updatedAt: 1 }).catch(console.error); - // Not strictly necessary, could use _id, but more convenient. Also for stats - conversations.createIndex({ createdAt: 1 }).catch(console.error); - // To do stats on conversation messages - conversations.createIndex({ "messages.createdAt": 1 }, { sparse: true }).catch(console.error); - // Unique index for stats - conversationStats - .createIndex( - { +export class Database { + private client: MongoClient; + + private static instance: Database; + + private constructor() { + if (!env.MONGODB_URL) { + throw new Error( + "Please specify the MONGODB_URL environment variable inside .env.local. Set it to mongodb://localhost:27017 if you are running MongoDB locally, or to a MongoDB Atlas free instance for example." + ); + } + + this.client = new MongoClient(env.MONGODB_URL, { + directConnection: env.MONGODB_DIRECT_CONNECTION === "true", + }); + + this.client.connect().catch((err) => { + logger.error("Connection error", err); + process.exit(1); + }); + this.client.db(env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : "")); + this.client.on("open", () => this.initDatabase()); + + // Disconnect DB on process kill + process.on("SIGINT", async () => { + await this.client.close(true); + + // https://github.com/sveltejs/kit/issues/9540 + setTimeout(() => { + process.exit(0); + }, 100); + }); + } + + public static getInstance(): Database { + if (!Database.instance) { + Database.instance = new Database(); + } + + return Database.instance; + } + + /** + * Return mongoClient + */ + public getClient(): MongoClient { + return this.client; + } + + /** + * Return map of database's collections + */ + public getCollections() { + const db = this.client.db( + env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : "") + ); + + const conversations = db.collection("conversations"); + const conversationStats = db.collection(CONVERSATION_STATS_COLLECTION); + const assistants = db.collection("assistants"); + const assistantStats = db.collection("assistants.stats"); + const reports = db.collection("reports"); + const sharedConversations = db.collection("sharedConversations"); + const abortedGenerations = db.collection("abortedGenerations"); + const settings = db.collection("settings"); + const users = db.collection("users"); + const sessions = db.collection("sessions"); + const messageEvents = db.collection("messageEvents"); + const bucket = new GridFSBucket(db, { bucketName: "files" }); + const migrationResults = db.collection("migrationResults"); + const semaphores = db.collection("semaphores"); + + return { + conversations, + conversationStats, + assistants, + assistantStats, + reports, + sharedConversations, + abortedGenerations, + settings, + users, + sessions, + messageEvents, + bucket, + migrationResults, + semaphores, + }; + } + + /** + * Init database once connected: Index creation + * @private + */ + private initDatabase() { + const { + conversations, + conversationStats, + assistants, + assistantStats, + reports, + sharedConversations, + abortedGenerations, + settings, + users, + sessions, + messageEvents, + semaphores, + } = this.getCollections(); + + conversations + .createIndex( + { sessionId: 1, updatedAt: -1 }, + { partialFilterExpression: { sessionId: { $exists: true } } } + ) + .catch(logger.error); + conversations + .createIndex( + { userId: 1, updatedAt: -1 }, + { partialFilterExpression: { userId: { $exists: true } } } + ) + .catch(logger.error); + conversations + .createIndex( + { "message.id": 1, "message.ancestors": 1 }, + { partialFilterExpression: { userId: { $exists: true } } } + ) + .catch(logger.error); + // Not strictly necessary, could use _id, but more convenient. Also for stats + // To do stats on conversation messages + conversations.createIndex({ "messages.createdAt": 1 }, { sparse: true }).catch(logger.error); + // Unique index for stats + conversationStats + .createIndex( + { + type: 1, + "date.field": 1, + "date.span": 1, + "date.at": 1, + distinct: 1, + }, + { unique: true } + ) + .catch(logger.error); + // Allow easy check of last computed stat for given type/dateField + conversationStats + .createIndex({ type: 1, "date.field": 1, - "date.span": 1, "date.at": 1, - distinct: 1, - }, - { unique: true } - ) - .catch(console.error); - // Allow easy check of last computed stat for given type/dateField - conversationStats - .createIndex({ - type: 1, - "date.field": 1, - "date.at": 1, - }) - .catch(console.error); - abortedGenerations.createIndex({ updatedAt: 1 }, { expireAfterSeconds: 30 }).catch(console.error); - abortedGenerations.createIndex({ conversationId: 1 }, { unique: true }).catch(console.error); - sharedConversations.createIndex({ hash: 1 }, { unique: true }).catch(console.error); - settings.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(console.error); - settings.createIndex({ userId: 1 }, { unique: true, sparse: true }).catch(console.error); - settings.createIndex({ assistants: 1 }).catch(console.error); - users.createIndex({ hfUserId: 1 }, { unique: true }).catch(console.error); - users.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(console.error); - // No unicity because due to renames & outdated info from oauth provider, there may be the same username on different users - users.createIndex({ username: 1 }).catch(console.error); - messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error); - sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error); - sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error); - assistants.createIndex({ createdById: 1, userCount: -1 }).catch(console.error); - assistants.createIndex({ userCount: 1 }).catch(console.error); - assistants.createIndex({ featured: 1, userCount: -1 }).catch(console.error); - assistants.createIndex({ modelId: 1, userCount: -1 }).catch(console.error); - assistants.createIndex({ searchTokens: 1 }).catch(console.error); - assistants.createIndex({ last24HoursCount: 1 }).catch(console.error); - assistantStats - // Order of keys is important for the queries - .createIndex({ "date.span": 1, "date.at": 1, assistantId: 1 }, { unique: true }) - .catch(console.error); - reports.createIndex({ assistantId: 1 }).catch(console.error); - reports.createIndex({ createdBy: 1, assistantId: 1 }).catch(console.error); - - // Unique index for semaphore and migration results - semaphores.createIndex({ key: 1 }, { unique: true }).catch(console.error); - semaphores.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error); -}); + }) + .catch(logger.error); + abortedGenerations + .createIndex({ updatedAt: 1 }, { expireAfterSeconds: 30 }) + .catch(logger.error); + abortedGenerations.createIndex({ conversationId: 1 }, { unique: true }).catch(logger.error); + sharedConversations.createIndex({ hash: 1 }, { unique: true }).catch(logger.error); + settings.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(logger.error); + settings.createIndex({ userId: 1 }, { unique: true, sparse: true }).catch(logger.error); + settings.createIndex({ assistants: 1 }).catch(logger.error); + users.createIndex({ hfUserId: 1 }, { unique: true }).catch(logger.error); + users.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(logger.error); + // No unicity because due to renames & outdated info from oauth provider, there may be the same username on different users + users.createIndex({ username: 1 }).catch(logger.error); + messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(logger.error); + sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(logger.error); + sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(logger.error); + assistants.createIndex({ createdById: 1, userCount: -1 }).catch(logger.error); + assistants.createIndex({ userCount: 1 }).catch(logger.error); + assistants.createIndex({ featured: 1, userCount: -1 }).catch(logger.error); + assistants.createIndex({ modelId: 1, userCount: -1 }).catch(logger.error); + assistants.createIndex({ searchTokens: 1 }).catch(logger.error); + assistants.createIndex({ last24HoursCount: 1 }).catch(logger.error); + assistantStats + // Order of keys is important for the queries + .createIndex({ "date.span": 1, "date.at": 1, assistantId: 1 }, { unique: true }) + .catch(logger.error); + reports.createIndex({ assistantId: 1 }).catch(logger.error); + reports.createIndex({ createdBy: 1, assistantId: 1 }).catch(logger.error); + + // Unique index for semaphore and migration results + semaphores.createIndex({ key: 1 }, { unique: true }).catch(logger.error); + semaphores.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(logger.error); + } +} + +export const collections = building + ? ({} as unknown as ReturnType) + : Database.getInstance().getCollections(); diff --git a/src/lib/server/embeddingEndpoints/hfApi/embeddingHfApi.ts b/src/lib/server/embeddingEndpoints/hfApi/embeddingHfApi.ts index 162e8964c00..86f84ac19b8 100644 --- a/src/lib/server/embeddingEndpoints/hfApi/embeddingHfApi.ts +++ b/src/lib/server/embeddingEndpoints/hfApi/embeddingHfApi.ts @@ -1,7 +1,8 @@ import { z } from "zod"; import type { EmbeddingEndpoint, Embedding } from "../embeddingEndpoints"; import { chunk } from "$lib/utils/chunk"; -import { HF_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; +import { logger } from "$lib/server/logger"; export const embeddingEndpointHfApiSchema = z.object({ weight: z.number().int().positive().default(1), @@ -10,7 +11,7 @@ export const embeddingEndpointHfApiSchema = z.object({ authorization: z .string() .optional() - .transform((v) => (!v && HF_TOKEN ? "Bearer " + HF_TOKEN : v)), // if the header is not set but HF_TOKEN is, use it as the authorization header + .transform((v) => (!v && env.HF_TOKEN ? "Bearer " + env.HF_TOKEN : v)), // if the header is not set but HF_TOKEN is, use it as the authorization header }); export async function embeddingEndpointHfApi( @@ -35,8 +36,8 @@ export async function embeddingEndpointHfApi( }); if (!response.ok) { - console.log(await response.text()); - console.error("Failed to get embeddings from Hugging Face API", response); + logger.error(await response.text()); + logger.error("Failed to get embeddings from Hugging Face API", response); return []; } diff --git a/src/lib/server/embeddingEndpoints/openai/embeddingEndpoints.ts b/src/lib/server/embeddingEndpoints/openai/embeddingEndpoints.ts index 89d7900bb28..527a9732498 100644 --- a/src/lib/server/embeddingEndpoints/openai/embeddingEndpoints.ts +++ b/src/lib/server/embeddingEndpoints/openai/embeddingEndpoints.ts @@ -1,14 +1,14 @@ import { z } from "zod"; import type { EmbeddingEndpoint, Embedding } from "../embeddingEndpoints"; import { chunk } from "$lib/utils/chunk"; -import { OPENAI_API_KEY } from "$env/static/private"; +import { env } from "$env/dynamic/private"; export const embeddingEndpointOpenAIParametersSchema = z.object({ weight: z.number().int().positive().default(1), model: z.any(), type: z.literal("openai"), url: z.string().url().default("https://api.openai.com/v1/embeddings"), - apiKey: z.string().default(OPENAI_API_KEY), + apiKey: z.string().default(env.OPENAI_API_KEY), }); export async function embeddingEndpointOpenAI( diff --git a/src/lib/server/embeddingEndpoints/tei/embeddingEndpoints.ts b/src/lib/server/embeddingEndpoints/tei/embeddingEndpoints.ts index 0d2c7ae1336..c999ceba7da 100644 --- a/src/lib/server/embeddingEndpoints/tei/embeddingEndpoints.ts +++ b/src/lib/server/embeddingEndpoints/tei/embeddingEndpoints.ts @@ -1,7 +1,8 @@ import { z } from "zod"; import type { EmbeddingEndpoint, Embedding } from "../embeddingEndpoints"; import { chunk } from "$lib/utils/chunk"; -import { HF_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; +import { logger } from "$lib/server/logger"; export const embeddingEndpointTeiParametersSchema = z.object({ weight: z.number().int().positive().default(1), @@ -11,7 +12,7 @@ export const embeddingEndpointTeiParametersSchema = z.object({ authorization: z .string() .optional() - .transform((v) => (!v && HF_TOKEN ? "Bearer " + HF_TOKEN : v)), // if the header is not set but HF_TOKEN is, use it as the authorization header + .transform((v) => (!v && env.HF_TOKEN ? "Bearer " + env.HF_TOKEN : v)), // if the header is not set but HF_TOKEN is, use it as the authorization header }); const getModelInfoByUrl = async (url: string, authorization?: string) => { @@ -29,7 +30,7 @@ const getModelInfoByUrl = async (url: string, authorization?: string) => { const json = await response.json(); return { max_client_batch_size: 32, max_batch_tokens: 16384, ...json }; } catch { - console.log("Could not get info from TEI embedding endpoint. Using defaults."); + logger.debug("Could not get info from TEI embedding endpoint. Using defaults."); return { max_client_batch_size: 32, max_batch_tokens: 16384 }; } }; diff --git a/src/lib/server/embeddingModels.ts b/src/lib/server/embeddingModels.ts index 96f3795bd93..67ad8fe5b1e 100644 --- a/src/lib/server/embeddingModels.ts +++ b/src/lib/server/embeddingModels.ts @@ -1,4 +1,4 @@ -import { TEXT_EMBEDDING_MODELS } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { z } from "zod"; import { sum } from "$lib/utils/sum"; @@ -29,7 +29,7 @@ const modelConfig = z.object({ // Default embedding model for backward compatibility const rawEmbeddingModelJSON = - TEXT_EMBEDDING_MODELS || + env.TEXT_EMBEDDING_MODELS || `[ { "name": "Xenova/gte-small", diff --git a/src/lib/server/endpoints/anthropic/endpointAnthropic.ts b/src/lib/server/endpoints/anthropic/endpointAnthropic.ts index e3ef2eb51fa..4353c6b11a5 100644 --- a/src/lib/server/endpoints/anthropic/endpointAnthropic.ts +++ b/src/lib/server/endpoints/anthropic/endpointAnthropic.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { ANTHROPIC_API_KEY } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import type { Endpoint } from "../endpoints"; import type { TextGenerationStreamOutput } from "@huggingface/inference"; @@ -8,7 +8,7 @@ export const endpointAnthropicParametersSchema = z.object({ model: z.any(), type: z.literal("anthropic"), baseURL: z.string().url().default("https://api.anthropic.com"), - apiKey: z.string().default(ANTHROPIC_API_KEY ?? "sk-"), + apiKey: z.string().default(env.ANTHROPIC_API_KEY ?? "sk-"), defaultHeaders: z.record(z.string()).optional(), defaultQuery: z.record(z.string()).optional(), }); diff --git a/src/lib/server/endpoints/cloudflare/endpointCloudflare.ts b/src/lib/server/endpoints/cloudflare/endpointCloudflare.ts index 3a1be4d5175..f09d2723adb 100644 --- a/src/lib/server/endpoints/cloudflare/endpointCloudflare.ts +++ b/src/lib/server/endpoints/cloudflare/endpointCloudflare.ts @@ -1,14 +1,15 @@ import { z } from "zod"; import type { Endpoint } from "../endpoints"; import type { TextGenerationStreamOutput } from "@huggingface/inference"; -import { CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; +import { logger } from "$lib/server/logger"; export const endpointCloudflareParametersSchema = z.object({ weight: z.number().int().positive().default(1), model: z.any(), type: z.literal("cloudflare"), - accountId: z.string().default(CLOUDFLARE_ACCOUNT_ID), - apiToken: z.string().default(CLOUDFLARE_API_TOKEN), + accountId: z.string().default(env.CLOUDFLARE_ACCOUNT_ID), + apiToken: z.string().default(env.CLOUDFLARE_API_TOKEN), }); export async function endpointCloudflare( @@ -104,8 +105,8 @@ export async function endpointCloudflare( try { data = JSON.parse(jsonString); } catch (e) { - console.error("Failed to parse JSON", e); - console.error("Problematic JSON string:", jsonString); + logger.error("Failed to parse JSON", e); + logger.error("Problematic JSON string:", jsonString); continue; // Skip this iteration and try the next chunk } diff --git a/src/lib/server/endpoints/cohere/endpointCohere.ts b/src/lib/server/endpoints/cohere/endpointCohere.ts index 524152fb991..f1c5562fa02 100644 --- a/src/lib/server/endpoints/cohere/endpointCohere.ts +++ b/src/lib/server/endpoints/cohere/endpointCohere.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { COHERE_API_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import type { Endpoint } from "../endpoints"; import type { TextGenerationStreamOutput } from "@huggingface/inference"; import type { Cohere, CohereClient } from "cohere-ai"; @@ -9,7 +9,7 @@ export const endpointCohereParametersSchema = z.object({ weight: z.number().int().positive().default(1), model: z.any(), type: z.literal("cohere"), - apiKey: z.string().default(COHERE_API_TOKEN), + apiKey: z.string().default(env.COHERE_API_TOKEN), raw: z.boolean().default(false), }); diff --git a/src/lib/server/endpoints/langserve/endpointLangserve.ts b/src/lib/server/endpoints/langserve/endpointLangserve.ts index 2c5a475c967..364765c478d 100644 --- a/src/lib/server/endpoints/langserve/endpointLangserve.ts +++ b/src/lib/server/endpoints/langserve/endpointLangserve.ts @@ -2,6 +2,7 @@ import { buildPrompt } from "$lib/buildPrompt"; import { z } from "zod"; import type { Endpoint } from "../endpoints"; import type { TextGenerationStreamOutput } from "@huggingface/inference"; +import { logger } from "$lib/server/logger"; export const endpointLangserveParametersSchema = z.object({ weight: z.number().int().positive().default(1), @@ -99,8 +100,8 @@ export function endpointLangserve( try { data = JSON.parse(jsonString); } catch (e) { - console.error("Failed to parse JSON", e); - console.error("Problematic JSON string:", jsonString); + logger.error("Failed to parse JSON", e); + logger.error("Problematic JSON string:", jsonString); continue; // Skip this iteration and try the next chunk } // Assuming content within data is a plain string diff --git a/src/lib/server/endpoints/llamacpp/endpointLlamacpp.ts b/src/lib/server/endpoints/llamacpp/endpointLlamacpp.ts index ffd9fa2c495..b2b8d1478c2 100644 --- a/src/lib/server/endpoints/llamacpp/endpointLlamacpp.ts +++ b/src/lib/server/endpoints/llamacpp/endpointLlamacpp.ts @@ -1,8 +1,9 @@ -import { HF_ACCESS_TOKEN, HF_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { buildPrompt } from "$lib/buildPrompt"; import type { TextGenerationStreamOutput } from "@huggingface/inference"; import type { Endpoint } from "../endpoints"; import { z } from "zod"; +import { logger } from "$lib/server/logger"; export const endpointLlamacppParametersSchema = z.object({ weight: z.number().int().positive().default(1), @@ -12,7 +13,7 @@ export const endpointLlamacppParametersSchema = z.object({ accessToken: z .string() .min(1) - .default(HF_TOKEN ?? HF_ACCESS_TOKEN), + .default(env.HF_TOKEN ?? env.HF_ACCESS_TOKEN), }); export function endpointLlamacpp( @@ -93,8 +94,8 @@ export function endpointLlamacpp( try { data = JSON.parse(jsonString); } catch (e) { - console.error("Failed to parse JSON", e); - console.error("Problematic JSON string:", jsonString); + logger.error("Failed to parse JSON", e); + logger.error("Problematic JSON string:", jsonString); continue; // Skip this iteration and try the next chunk } diff --git a/src/lib/server/endpoints/openai/endpointOai.ts b/src/lib/server/endpoints/openai/endpointOai.ts index 8bd28540dd4..945921b1b9a 100644 --- a/src/lib/server/endpoints/openai/endpointOai.ts +++ b/src/lib/server/endpoints/openai/endpointOai.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { openAICompletionToTextGenerationStream } from "./openAICompletionToTextGenerationStream"; import { openAIChatToTextGenerationStream } from "./openAIChatToTextGenerationStream"; import { buildPrompt } from "$lib/buildPrompt"; -import { OPENAI_API_KEY } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import type { Endpoint } from "../endpoints"; export const endpointOAIParametersSchema = z.object({ @@ -10,7 +10,7 @@ export const endpointOAIParametersSchema = z.object({ model: z.any(), type: z.literal("openai"), baseURL: z.string().url().default("https://api.openai.com/v1"), - apiKey: z.string().default(OPENAI_API_KEY ?? "sk-"), + apiKey: z.string().default(env.OPENAI_API_KEY ?? "sk-"), completion: z .union([z.literal("completions"), z.literal("chat_completions")]) .default("chat_completions"), diff --git a/src/lib/server/endpoints/tgi/endpointTgi.ts b/src/lib/server/endpoints/tgi/endpointTgi.ts index 131d628ae21..aed06739722 100644 --- a/src/lib/server/endpoints/tgi/endpointTgi.ts +++ b/src/lib/server/endpoints/tgi/endpointTgi.ts @@ -1,4 +1,4 @@ -import { HF_ACCESS_TOKEN, HF_TOKEN } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { buildPrompt } from "$lib/buildPrompt"; import { textGenerationStream } from "@huggingface/inference"; import type { Endpoint } from "../endpoints"; @@ -9,7 +9,7 @@ export const endpointTgiParametersSchema = z.object({ model: z.any(), type: z.literal("tgi"), url: z.string().url(), - accessToken: z.string().default(HF_TOKEN ?? HF_ACCESS_TOKEN), + accessToken: z.string().default(env.HF_TOKEN ?? env.HF_ACCESS_TOKEN), authorization: z.string().optional(), }); diff --git a/src/lib/server/files/downloadFile.ts b/src/lib/server/files/downloadFile.ts index 4d2bddb1c30..91b430fc5d8 100644 --- a/src/lib/server/files/downloadFile.ts +++ b/src/lib/server/files/downloadFile.ts @@ -1,5 +1,5 @@ import { error } from "@sveltejs/kit"; -import { collections } from "../database"; +import { collections } from "$lib/server/database"; import type { Conversation } from "$lib/types/Conversation"; import type { SharedConversation } from "$lib/types/SharedConversation"; diff --git a/src/lib/server/files/uploadFile.ts b/src/lib/server/files/uploadFile.ts index 1c4a59b6f44..34452245741 100644 --- a/src/lib/server/files/uploadFile.ts +++ b/src/lib/server/files/uploadFile.ts @@ -1,6 +1,6 @@ import type { Conversation } from "$lib/types/Conversation"; import { sha256 } from "$lib/utils/sha256"; -import { collections } from "../database"; +import { collections } from "$lib/server/database"; export async function uploadFile(file: Blob, conv: Conversation): Promise { const sha = await sha256(await file.text()); diff --git a/src/lib/server/logger.ts b/src/lib/server/logger.ts new file mode 100644 index 00000000000..b01b7692e3a --- /dev/null +++ b/src/lib/server/logger.ts @@ -0,0 +1,18 @@ +import pino from "pino"; +import { dev } from "$app/environment"; +import { env } from "$env/dynamic/private"; + +let options: pino.LoggerOptions = {}; + +if (dev) { + options = { + transport: { + target: "pino-pretty", + options: { + colorize: true, + }, + }, + }; +} + +export const logger = pino({ ...options, level: env.LOG_LEVEL ?? "info" }); diff --git a/src/lib/server/metrics.ts b/src/lib/server/metrics.ts new file mode 100644 index 00000000000..a4728cc78ba --- /dev/null +++ b/src/lib/server/metrics.ts @@ -0,0 +1,43 @@ +import { collectDefaultMetrics, Registry } from "prom-client"; +import express from "express"; +import { logger } from "$lib/server/logger"; +import { env } from "$env/dynamic/private"; + +export class MetricsServer { + private static instance: MetricsServer; + + private constructor() { + const app = express(); + const port = env.METRICS_PORT || "5565"; + + const server = app.listen(port, () => { + logger.info(`Metrics server listening on port ${port}`); + }); + + const register = new Registry(); + collectDefaultMetrics({ register }); + + app.get("/metrics", (req, res) => { + register.metrics().then((metrics) => { + res.set("Content-Type", "text/plain"); + res.send(metrics); + }); + }); + + process.on("SIGINT", async () => { + logger.info("Sigint received, disconnect metrics server ..."); + server.close(() => { + logger.info("Server stopped ..."); + }); + process.exit(); + }); + } + + public static getInstance(): MetricsServer { + if (!MetricsServer.instance) { + MetricsServer.instance = new MetricsServer(); + } + + return MetricsServer.instance; + } +} diff --git a/src/lib/server/models.ts b/src/lib/server/models.ts index 20bd58645d0..7cdbaf5f5bb 100644 --- a/src/lib/server/models.ts +++ b/src/lib/server/models.ts @@ -1,11 +1,4 @@ -import { - HF_TOKEN, - HF_API_ROOT, - MODELS, - OLD_MODELS, - TASK_MODEL, - HF_ACCESS_TOKEN, -} from "$env/static/private"; +import { env } from "$env/dynamic/private"; import type { ChatTemplateInput } from "$lib/types/Template"; import { compileTemplate } from "$lib/utils/template"; import { z } from "zod"; @@ -18,6 +11,7 @@ import type { PreTrainedTokenizer } from "@xenova/transformers"; import JSON5 from "json5"; import { getTokenizer } from "$lib/utils/getTokenizer"; +import { logger } from "$lib/server/logger"; type Optional = Pick, K> & Omit; @@ -71,7 +65,7 @@ const modelConfig = z.object({ embeddingModel: validateEmbeddingModelByName(embeddingModels).optional(), }); -const modelsRaw = z.array(modelConfig).parse(JSON5.parse(MODELS)); +const modelsRaw = z.array(modelConfig).parse(JSON5.parse(env.MODELS)); async function getChatPromptRender( m: z.infer @@ -91,7 +85,7 @@ async function getChatPromptRender( try { tokenizer = await getTokenizer(m.tokenizer); } catch (e) { - console.error( + logger.error( "Failed to load tokenizer for model " + m.name + " consider setting chatPromptTemplate manually or making sure the model is available on the hub. Error: " + @@ -146,8 +140,8 @@ const addEndpoint = (m: Awaited>) => ({ if (!m.endpoints) { return endpointTgi({ type: "tgi", - url: `${HF_API_ROOT}/${m.name}`, - accessToken: HF_TOKEN ?? HF_ACCESS_TOKEN, + url: `${env.HF_API_ROOT}/${m.name}`, + accessToken: env.HF_TOKEN ?? env.HF_ACCESS_TOKEN, weight: 1, model: m, }); @@ -198,7 +192,7 @@ export const models = await Promise.all(modelsRaw.map((e) => processModel(e).the export const defaultModel = models[0]; // Models that have been deprecated -export const oldModels = OLD_MODELS +export const oldModels = env.OLD_MODELS ? z .array( z.object({ @@ -207,7 +201,7 @@ export const oldModels = OLD_MODELS displayName: z.string().min(1).optional(), }) ) - .parse(JSON5.parse(OLD_MODELS)) + .parse(JSON5.parse(env.OLD_MODELS)) .map((m) => ({ ...m, id: m.id || m.name, displayName: m.displayName || m.name })) : []; @@ -218,9 +212,9 @@ export const validateModel = (_models: BackendModel[]) => { // if `TASK_MODEL` is string & name of a model in `MODELS`, then we use `MODELS[TASK_MODEL]`, else we try to parse `TASK_MODEL` as a model config itself -export const smallModel = TASK_MODEL - ? (models.find((m) => m.name === TASK_MODEL) || - (await processModel(modelConfig.parse(JSON5.parse(TASK_MODEL))).then((m) => +export const smallModel = env.TASK_MODEL + ? (models.find((m) => m.name === env.TASK_MODEL) || + (await processModel(modelConfig.parse(JSON5.parse(env.TASK_MODEL))).then((m) => addEndpoint(m) ))) ?? defaultModel diff --git a/src/lib/server/preprocessMessages.ts b/src/lib/server/preprocessMessages.ts index 53768fa6f62..9cdcaae0487 100644 --- a/src/lib/server/preprocessMessages.ts +++ b/src/lib/server/preprocessMessages.ts @@ -2,6 +2,7 @@ import type { Conversation } from "$lib/types/Conversation"; import type { Message } from "$lib/types/Message"; import { format } from "date-fns"; import { downloadFile } from "./files/downloadFile"; +import { logger } from "$lib/server/logger"; export async function preprocessMessages( messages: Message[], @@ -44,7 +45,7 @@ Answer the question: ${lastQuestion}`; const b64 = image.toString("base64"); return `![](data:${mime};base64,${b64})})`; } catch (e) { - console.error(e); + logger.error(e); } }) ); diff --git a/src/lib/server/summarize.ts b/src/lib/server/summarize.ts index 264963614f1..4cef6174dc9 100644 --- a/src/lib/server/summarize.ts +++ b/src/lib/server/summarize.ts @@ -1,9 +1,10 @@ -import { LLM_SUMMERIZATION } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { generateFromDefaultEndpoint } from "$lib/server/generateFromDefaultEndpoint"; import type { Message } from "$lib/types/Message"; +import { logger } from "$lib/server/logger"; export async function summarize(prompt: string) { - if (!LLM_SUMMERIZATION) { + if (!env.LLM_SUMMERIZATION) { return prompt.split(/\s+/g).slice(0, 5).join(" "); } @@ -41,7 +42,7 @@ export async function summarize(prompt: string) { return summary; }) .catch((e) => { - console.error(e); + logger.error(e); return null; }); } diff --git a/src/lib/server/usageLimits.ts b/src/lib/server/usageLimits.ts index 0323e83fb50..e1f2390388a 100644 --- a/src/lib/server/usageLimits.ts +++ b/src/lib/server/usageLimits.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { USAGE_LIMITS, RATE_LIMIT } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import JSON5 from "json5"; // RATE_LIMIT is the legacy way to define messages per minute limit @@ -12,7 +12,7 @@ export const usageLimitsSchema = z messagesPerMinute: z .preprocess((val) => { if (val === undefined) { - return RATE_LIMIT; + return env.RATE_LIMIT; } return val; }, z.coerce.number().optional()) @@ -20,4 +20,4 @@ export const usageLimitsSchema = z }) .optional(); -export const usageLimits = usageLimitsSchema.parse(JSON5.parse(USAGE_LIMITS)); +export const usageLimits = usageLimitsSchema.parse(JSON5.parse(env.USAGE_LIMITS)); diff --git a/src/lib/server/websearch/runWebSearch.ts b/src/lib/server/websearch/runWebSearch.ts index 8eba2ddd627..1d06a39c5a8 100644 --- a/src/lib/server/websearch/runWebSearch.ts +++ b/src/lib/server/websearch/runWebSearch.ts @@ -5,7 +5,7 @@ import { chunk } from "$lib/utils/chunk"; import { findSimilarSentences } from "$lib/server/sentenceSimilarity"; import { getWebSearchProvider } from "./searchWeb"; import { defaultEmbeddingModel, embeddingModels } from "$lib/server/embeddingModels"; -import { WEBSEARCH_ALLOWLIST, WEBSEARCH_BLOCKLIST, ENABLE_LOCAL_FETCH } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import type { Conversation } from "$lib/types/Conversation"; import type { MessageUpdate } from "$lib/types/MessageUpdate"; @@ -22,8 +22,8 @@ const MAX_N_PAGES_EMBED = 5 as const; const listSchema = z.array(z.string()).default([]); -const allowList = listSchema.parse(JSON5.parse(WEBSEARCH_ALLOWLIST)); -const blockList = listSchema.parse(JSON5.parse(WEBSEARCH_BLOCKLIST)); +const allowList = listSchema.parse(JSON5.parse(env.WEBSEARCH_ALLOWLIST)); +const blockList = listSchema.parse(JSON5.parse(env.WEBSEARCH_BLOCKLIST)); export async function runWebSearch( conv: Conversation, @@ -52,7 +52,7 @@ export async function runWebSearch( let linksToUse = [...ragSettings.allowedLinks]; - if (ENABLE_LOCAL_FETCH !== "true") { + if (env.ENABLE_LOCAL_FETCH !== "true") { const localLinks = await Promise.all( linksToUse.map(async (link) => { try { diff --git a/src/lib/server/websearch/searchSearxng.ts b/src/lib/server/websearch/searchSearxng.ts index a432003cb16..9507d536969 100644 --- a/src/lib/server/websearch/searchSearxng.ts +++ b/src/lib/server/websearch/searchSearxng.ts @@ -1,11 +1,12 @@ -import { SEARXNG_QUERY_URL } from "$env/static/private"; +import { env } from "$env/dynamic/private"; +import { logger } from "$lib/server/logger"; export async function searchSearxng(query: string) { const abortController = new AbortController(); setTimeout(() => abortController.abort(), 10000); // Insert the query into the URL template - let url = SEARXNG_QUERY_URL.replace("", query); + let url = env.SEARXNG_QUERY_URL.replace("", query); // Check if "&format=json" already exists in the URL if (!url.includes("&format=json")) { @@ -18,7 +19,7 @@ export async function searchSearxng(query: string) { }) .then((response) => response.json() as Promise<{ results: { url: string }[] }>) .catch((error) => { - console.error("Failed to fetch or parse JSON", error); + logger.error("Failed to fetch or parse JSON", error); throw new Error("Failed to fetch or parse JSON"); }); diff --git a/src/lib/server/websearch/searchWeb.ts b/src/lib/server/websearch/searchWeb.ts index 94021e5c014..724be1227a7 100644 --- a/src/lib/server/websearch/searchWeb.ts +++ b/src/lib/server/websearch/searchWeb.ts @@ -1,13 +1,6 @@ import type { YouWebSearch } from "../../types/WebSearch"; import { WebSearchProvider } from "../../types/WebSearch"; -import { - SERPAPI_KEY, - SERPER_API_KEY, - SERPSTACK_API_KEY, - USE_LOCAL_WEBSEARCH, - SEARXNG_QUERY_URL, - YDC_API_KEY, -} from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { getJson } from "serpapi"; import type { GoogleParameters } from "serpapi"; import { searchWebLocal } from "./searchWebLocal"; @@ -15,9 +8,9 @@ import { searchSearxng } from "./searchSearxng"; // get which SERP api is providing web results export function getWebSearchProvider() { - if (YDC_API_KEY) { + if (env.YDC_API_KEY) { return WebSearchProvider.YOU; - } else if (SEARXNG_QUERY_URL) { + } else if (env.SEARXNG_QUERY_URL) { return WebSearchProvider.SEARXNG; } else { return WebSearchProvider.GOOGLE; @@ -26,22 +19,22 @@ export function getWebSearchProvider() { // Show result as JSON export async function searchWeb(query: string) { - if (USE_LOCAL_WEBSEARCH) { + if (env.USE_LOCAL_WEBSEARCH) { return await searchWebLocal(query); } - if (SEARXNG_QUERY_URL) { + if (env.SEARXNG_QUERY_URL) { return await searchSearxng(query); } - if (SERPER_API_KEY) { + if (env.SERPER_API_KEY) { return await searchWebSerper(query); } - if (YDC_API_KEY) { + if (env.YDC_API_KEY) { return await searchWebYouApi(query); } - if (SERPAPI_KEY) { + if (env.SERPAPI_KEY) { return await searchWebSerpApi(query); } - if (SERPSTACK_API_KEY) { + if (env.SERPSTACK_API_KEY) { return await searchSerpStack(query); } throw new Error("No You.com or Serper.dev or SerpAPI key found"); @@ -58,7 +51,7 @@ export async function searchWebSerper(query: string) { method: "POST", body: JSON.stringify(params), headers: { - "x-api-key": SERPER_API_KEY, + "x-api-key": env.SERPER_API_KEY, "Content-type": "application/json; charset=UTF-8", }, }); @@ -84,7 +77,7 @@ export async function searchWebSerpApi(query: string) { hl: "en", gl: "us", google_domain: "google.com", - api_key: SERPAPI_KEY, + api_key: env.SERPAPI_KEY, } satisfies GoogleParameters; // Show result as JSON @@ -97,7 +90,7 @@ export async function searchWebYouApi(query: string) { const response = await fetch(`https://api.ydc-index.io/search?query=${query}`, { method: "GET", headers: { - "X-API-Key": YDC_API_KEY, + "X-API-Key": env.YDC_API_KEY, "Content-type": "application/json; charset=UTF-8", }, }); @@ -123,7 +116,7 @@ export async function searchWebYouApi(query: string) { export async function searchSerpStack(query: string) { const response = await fetch( - `http://api.serpstack.com/search?access_key=${SERPSTACK_API_KEY}&query=${query}&hl=en&gl=us`, + `http://api.serpstack.com/search?access_key=${env.SERPSTACK_API_KEY}&query=${query}&hl=en&gl=us`, { method: "GET", headers: { diff --git a/src/lib/utils/getShareUrl.ts b/src/lib/utils/getShareUrl.ts index ef4259f6ad3..5278ab6fd6e 100644 --- a/src/lib/utils/getShareUrl.ts +++ b/src/lib/utils/getShareUrl.ts @@ -1,6 +1,8 @@ import { base } from "$app/paths"; -import { PUBLIC_ORIGIN, PUBLIC_SHARE_PREFIX } from "$env/static/public"; +import { env as envPublic } from "$env/dynamic/public"; export function getShareUrl(url: URL, shareId: string): string { - return `${PUBLIC_SHARE_PREFIX || `${PUBLIC_ORIGIN || url.origin}${base}`}/r/${shareId}`; + return `${ + envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || url.origin}${base}` + }/r/${shareId}`; } diff --git a/src/lib/utils/isHuggingChat.ts b/src/lib/utils/isHuggingChat.ts index fbcbefbc546..df1ad80039e 100644 --- a/src/lib/utils/isHuggingChat.ts +++ b/src/lib/utils/isHuggingChat.ts @@ -1,3 +1,3 @@ -import { PUBLIC_APP_ASSETS } from "$env/static/public"; +import { env as envPublic } from "$env/dynamic/public"; -export const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat"; +export const isHuggingChat = envPublic.PUBLIC_APP_ASSETS === "huggingchat"; diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 6836376aa41..376412a7ffd 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -11,5 +11,10 @@

{$page.status}

{$page.error?.message}

+ {#if $page.error?.errorId} +
+
{$page.error
+					.errorId}
+ {/if}
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index c353bc4f47c..a79c470f688 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -5,17 +5,7 @@ import { UrlDependency } from "$lib/types/UrlDependency"; import { defaultModel, models, oldModels, validateModel } from "$lib/server/models"; import { authCondition, requiresUser } from "$lib/server/auth"; import { DEFAULT_SETTINGS } from "$lib/types/Settings"; -import { - SERPAPI_KEY, - SERPER_API_KEY, - SERPSTACK_API_KEY, - MESSAGES_BEFORE_LOGIN, - YDC_API_KEY, - USE_LOCAL_WEBSEARCH, - SEARXNG_QUERY_URL, - ENABLE_ASSISTANTS, - ENABLE_ASSISTANTS_RAG, -} from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { ObjectId } from "mongodb"; import type { ConvSidebar } from "$lib/types/ConvSidebar"; @@ -47,7 +37,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => { }); } - const enableAssistants = ENABLE_ASSISTANTS === "true"; + const enableAssistants = env.ENABLE_ASSISTANTS === "true"; const assistantActive = !models.map(({ id }) => id).includes(settings?.activeModel ?? ""); @@ -87,7 +77,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => { const assistants = await collections.assistants.find({ _id: { $in: assistantIds } }).toArray(); - const messagesBeforeLogin = MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0; + const messagesBeforeLogin = env.MESSAGES_BEFORE_LOGIN ? parseInt(env.MESSAGES_BEFORE_LOGIN) : 0; let loginRequired = false; @@ -136,12 +126,12 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => { }) satisfies ConvSidebar[], settings: { searchEnabled: !!( - SERPAPI_KEY || - SERPER_API_KEY || - SERPSTACK_API_KEY || - YDC_API_KEY || - USE_LOCAL_WEBSEARCH || - SEARXNG_QUERY_URL + env.SERPAPI_KEY || + env.SERPER_API_KEY || + env.SERPSTACK_API_KEY || + env.YDC_API_KEY || + env.USE_LOCAL_WEBSEARCH || + env.SEARXNG_QUERY_URL ), ethicsModalAccepted: !!settings?.ethicsModalAcceptedAt, ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null, @@ -188,7 +178,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => { }, assistant, enableAssistants, - enableAssistantsRAG: ENABLE_ASSISTANTS_RAG === "true", + enableAssistantsRAG: env.ENABLE_ASSISTANTS_RAG === "true", loginRequired, loginEnabled: requiresUser, guestMode: requiresUser && messagesBeforeLogin > 0, diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index bcfa7c05f37..18f9a884b26 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,13 +7,7 @@ import { page } from "$app/stores"; import { browser } from "$app/environment"; - import { - PUBLIC_APPLE_APP_ID, - PUBLIC_APP_DESCRIPTION, - PUBLIC_ORIGIN, - PUBLIC_PLAUSIBLE_SCRIPT_URL, - } from "$env/static/public"; - import { PUBLIC_APP_ASSETS, PUBLIC_APP_NAME } from "$env/static/public"; + import { env as envPublic } from "$env/dynamic/public"; import { error } from "$lib/stores/errors"; import { createSettingsStore } from "$lib/stores/settings"; @@ -134,7 +128,7 @@ - {PUBLIC_APP_NAME} + {envPublic.PUBLIC_APP_NAME} @@ -142,44 +136,49 @@ {#if !$page.url.pathname.includes("/assistant/") && $page.route.id !== "/assistants" && !$page.url.pathname.includes("/models/")} - + - + - + {/if} - {#if PUBLIC_PLAUSIBLE_SCRIPT_URL && PUBLIC_ORIGIN} + {#if envPublic.PUBLIC_PLAUSIBLE_SCRIPT_URL && envPublic.PUBLIC_ORIGIN} {/if} - {#if PUBLIC_APPLE_APP_ID} - + {#if envPublic.PUBLIC_APPLE_APP_ID} + {/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d0410802aa4..abedc1c8862 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,7 @@ - {PUBLIC_APP_NAME} + {envPublic.PUBLIC_APP_NAME} " -H "Content-Type: application/json" -d '{"model": "OpenAssistant/oasst-sft-6-llama-30b-xor"}' export async function POST({ request }) { - if (!PARQUET_EXPORT_DATASET || !PARQUET_EXPORT_HF_TOKEN) { + if (!env.PARQUET_EXPORT_DATASET || !env.PARQUET_EXPORT_HF_TOKEN) { throw error(500, "Parquet export is not configured."); } @@ -41,7 +42,7 @@ export async function POST({ request }) { const writer = await parquet.ParquetWriter.openFile(schema, fileName); let count = 0; - console.log("Exporting conversations for model", model); + logger.info("Exporting conversations for model", model); for await (const conversation of collections.settings.aggregate<{ title: string; @@ -88,11 +89,11 @@ export async function POST({ request }) { ++count; if (count % 1_000 === 0) { - console.log("Exported", count, "conversations"); + logger.info("Exported", count, "conversations"); } } - console.log("exporting convos with userId"); + logger.info("exporting convos with userId"); for await (const conversation of collections.settings.aggregate<{ title: string; @@ -133,24 +134,24 @@ export async function POST({ request }) { ++count; if (count % 1_000 === 0) { - console.log("Exported", count, "conversations"); + logger.info("Exported", count, "conversations"); } } await writer.close(); - console.log("Uploading", fileName, "to Hugging Face Hub"); + logger.info("Uploading", fileName, "to Hugging Face Hub"); await uploadFile({ file: pathToFileURL(fileName) as URL, - credentials: { accessToken: PARQUET_EXPORT_HF_TOKEN }, + credentials: { accessToken: env.PARQUET_EXPORT_HF_TOKEN }, repo: { type: "dataset", - name: PARQUET_EXPORT_DATASET, + name: env.PARQUET_EXPORT_DATASET, }, }); - console.log("Upload done"); + logger.info("Upload done"); await unlink(fileName); diff --git a/src/routes/admin/stats/compute/+server.ts b/src/routes/admin/stats/compute/+server.ts index 5e73c36c26a..d8d7f0ec605 100644 --- a/src/routes/admin/stats/compute/+server.ts +++ b/src/routes/admin/stats/compute/+server.ts @@ -1,15 +1,16 @@ import { json } from "@sveltejs/kit"; import type { ConversationStats } from "$lib/types/ConversationStats"; -import { CONVERSATION_STATS_COLLECTION, collections } from "$lib/server/database.js"; +import { CONVERSATION_STATS_COLLECTION, collections } from "$lib/server/database"; +import { logger } from "$lib/server/logger"; // Triger like this: // curl -X POST "http://localhost:5173/chat/admin/stats/compute" -H "Authorization: Bearer " export async function POST() { for (const span of ["day", "week", "month"] as const) { - computeStats({ dateField: "updatedAt", type: "conversation", span }).catch(console.error); - computeStats({ dateField: "createdAt", type: "conversation", span }).catch(console.error); - computeStats({ dateField: "createdAt", type: "message", span }).catch(console.error); + computeStats({ dateField: "updatedAt", type: "conversation", span }).catch(logger.error); + computeStats({ dateField: "createdAt", type: "conversation", span }).catch(logger.error); + computeStats({ dateField: "createdAt", type: "message", span }).catch(logger.error); } return json({}, { status: 202 }); @@ -29,7 +30,7 @@ async function computeStats(params: { // In those cases we need to compute the stats from before the last month as everything is one aggregation const minDate = lastComputed ? lastComputed.date.at : new Date(0); - console.log("Computing stats for", params.type, params.span, params.dateField, "from", minDate); + logger.info("Computing stats for", params.type, params.span, params.dateField, "from", minDate); const dateField = params.type === "message" ? "messages." + params.dateField : params.dateField; @@ -213,5 +214,5 @@ async function computeStats(params: { await collections.conversations.aggregate(pipeline, { allowDiskUse: true }).next(); - console.log("Computed stats for", params.type, params.span, params.dateField); + logger.info("Computed stats for", params.type, params.span, params.dateField); } diff --git a/src/routes/api/assistants/+server.ts b/src/routes/api/assistants/+server.ts index 8b99a680bdf..ac588676cbc 100644 --- a/src/routes/api/assistants/+server.ts +++ b/src/routes/api/assistants/+server.ts @@ -1,9 +1,9 @@ -import { collections } from "$lib/server/database.js"; +import { collections } from "$lib/server/database"; import type { Assistant } from "$lib/types/Assistant"; import type { User } from "$lib/types/User"; import { generateQueryTokens } from "$lib/utils/searchTokens.js"; import type { Filter } from "mongodb"; -import { REQUIRE_FEATURED_ASSISTANTS } from "$env/static/private"; +import { env } from "$env/dynamic/private"; const NUM_PER_PAGE = 24; @@ -27,11 +27,11 @@ export async function GET({ url, locals }) { // if there is no user, we show community assistants, so only show featured assistants const shouldBeFeatured = - REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? { featured: true } : {}; + env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? { featured: true } : {}; // if the user queried is not the current user, only show "public" assistants that have been shared before const shouldHaveBeenShared = - REQUIRE_FEATURED_ASSISTANTS === "true" && !createdByCurrentUser + env.REQUIRE_FEATURED_ASSISTANTS === "true" && !createdByCurrentUser ? { userCount: { $gt: 1 } } : {}; diff --git a/src/routes/assistant/[assistantId]/+page.server.ts b/src/routes/assistant/[assistantId]/+page.server.ts index ac14877dbc4..fddb181b4b8 100644 --- a/src/routes/assistant/[assistantId]/+page.server.ts +++ b/src/routes/assistant/[assistantId]/+page.server.ts @@ -1,5 +1,5 @@ import { base } from "$app/paths"; -import { collections } from "$lib/server/database.js"; +import { collections } from "$lib/server/database"; import { redirect } from "@sveltejs/kit"; import { ObjectId } from "mongodb"; diff --git a/src/routes/assistant/[assistantId]/+page.svelte b/src/routes/assistant/[assistantId]/+page.svelte index 16608d07ef2..f19b879023b 100644 --- a/src/routes/assistant/[assistantId]/+page.svelte +++ b/src/routes/assistant/[assistantId]/+page.svelte @@ -6,7 +6,7 @@ import { useSettingsStore } from "$lib/stores/settings"; import type { PageData } from "./$types"; import { applyAction, enhance } from "$app/forms"; - import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public"; + import { env as envPublic } from "$env/dynamic/public"; import { page } from "$app/stores"; import IconGear from "~icons/bi/gear-fill"; @@ -24,15 +24,16 @@ - + diff --git a/src/routes/assistants/+page.server.ts b/src/routes/assistants/+page.server.ts index a30c964a8d4..0cf3663f704 100644 --- a/src/routes/assistants/+page.server.ts +++ b/src/routes/assistants/+page.server.ts @@ -1,6 +1,6 @@ import { base } from "$app/paths"; -import { ENABLE_ASSISTANTS, REQUIRE_FEATURED_ASSISTANTS } from "$env/static/private"; -import { collections } from "$lib/server/database.js"; +import { env } from "$env/dynamic/private"; +import { Database, collections } from "$lib/server/database.js"; import { SortKey, type Assistant } from "$lib/types/Assistant"; import type { User } from "$lib/types/User"; import { generateQueryTokens } from "$lib/utils/searchTokens.js"; @@ -10,7 +10,7 @@ import type { Filter } from "mongodb"; const NUM_PER_PAGE = 24; export const load = async ({ url, locals }) => { - if (!ENABLE_ASSISTANTS) { + if (!env.ENABLE_ASSISTANTS) { throw redirect(302, `${base}/`); } @@ -18,7 +18,7 @@ export const load = async ({ url, locals }) => { const pageIndex = parseInt(url.searchParams.get("p") ?? "0"); const username = url.searchParams.get("user"); const query = url.searchParams.get("q")?.trim() ?? null; - const sort = url.searchParams.get("sort")?.trim() ?? SortKey.POPULAR; + const sort = url.searchParams.get("sort")?.trim() ?? SortKey.TRENDING; const createdByCurrentUser = locals.user?.username && locals.user.username === username; let user: Pick | null = null; @@ -34,11 +34,11 @@ export const load = async ({ url, locals }) => { // if there is no user, we show community assistants, so only show featured assistants const shouldBeFeatured = - REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? { featured: true } : {}; + env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? { featured: true } : {}; // if the user queried is not the current user, only show "public" assistants that have been shared before const shouldHaveBeenShared = - REQUIRE_FEATURED_ASSISTANTS === "true" && !createdByCurrentUser + env.REQUIRE_FEATURED_ASSISTANTS === "true" && !createdByCurrentUser ? { userCount: { $gt: 1 } } : {}; @@ -50,8 +50,9 @@ export const load = async ({ url, locals }) => { ...shouldBeFeatured, ...shouldHaveBeenShared, }; - const assistants = await collections.assistants - .find(filter) + const assistants = await Database.getInstance() + .getCollections() + .assistants.find(filter) .skip(NUM_PER_PAGE * pageIndex) .sort({ ...(sort === SortKey.TRENDING && { last24HoursCount: -1 }), @@ -60,7 +61,9 @@ export const load = async ({ url, locals }) => { .limit(NUM_PER_PAGE) .toArray(); - const numTotalItems = await collections.assistants.countDocuments(filter); + const numTotalItems = await Database.getInstance() + .getCollections() + .assistants.countDocuments(filter); return { assistants: JSON.parse(JSON.stringify(assistants)) as Array, diff --git a/src/routes/assistants/+page.svelte b/src/routes/assistants/+page.svelte index b27524aacc3..d2f90b6323c 100644 --- a/src/routes/assistants/+page.svelte +++ b/src/routes/assistants/+page.svelte @@ -1,7 +1,7 @@ - + - + diff --git a/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte b/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte index b76e362af73..86af4085e1f 100644 --- a/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +++ b/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte @@ -1,5 +1,5 @@